Plain MQTT with a hand-rolled binary protocol forces every consumer to independently maintain serialisation logic, documentation, and versioning. This explodes in complexity as the number of topics and team members grows.
ProtoMQ eliminates that by making Protobuf the contract enforced at the broker layer:
| Concern | Plain MQTT + custom binary | ProtoMQ |
|---|---|---|
| Schema definition | Ad-hoc, lives in docs or code comments | Canonical .proto files, version-controlled |
| Consumer bootstrap | Pre-shared, out-of-band | Built-in Service Discovery ($SYS/discovery/request) |
| Type safety | Manual, silently fails on mismatch | Validated at every PUBLISH |
| CLI tooling | Write your own encoder | protomq-cli publish --json '{"temp": 22.5}' |
| Observability | None | Admin HTTP API: connections, throughput, active schemas |
When it is not the right choice: if you control all producers and consumers end-to-end and have no schema-evolution needs, plain MQTT + a single struct is simpler. ProtoMQ's value scales with the number of independently-developed clients and the pace of schema change.
The canonical deployment target is a Linux host running systemd. The zig build system handles both compilation and service-file installation in one step.
Prerequisites on the remote host: Zig 0.15.2+, git.
# 1. Clone to the installation directory
sudo mkdir -p /opt/protomq && sudo chown $USER /opt/protomq
git clone https://github.com/electricalgorithm/protomq /opt/protomq
# 2. Build and install (cross-compile on the remote host itself)
# The --prefix flag installs binaries to /opt/protomq/bin/
# and the systemd unit to /opt/protomq/etc/systemd/system/
cd /opt/protomq
zig build -Doptimize=ReleaseSafe -Dadmin_server=true --prefix /opt/protomq
# 3. Register and start the systemd service
sudo ln -sf /opt/protomq/etc/systemd/system/protomq.service \
/etc/systemd/system/protomq.service
sudo systemctl daemon-reload
sudo systemctl enable --now protomq
# 4. Verify
systemctl status protomq
# Expected: Active: active (running)The service runs the binary at /opt/protomq/bin/protomq-server from the /opt/protomq working directory (so it resolves the schemas/ directory correctly) and restarts automatically on failure.
Validation — from any machine that can reach the server:
# MQTT connectivity
./zig-out/bin/protomq-cli connect --host <remote_host>
# Admin API (runs only on loopback; tunnel or run on the host)
curl -s -H "Authorization: Bearer admin_secret" \
http://127.0.0.1:8080/metrics
# → {"connections":0,"messages_routed":0,"schemas":1,"memory_mb":0}Security note: The Admin Server binds to
127.0.0.1:8080only. Set theADMIN_TOKENenvironment variable in the systemd unit before exposing it via an SSH tunnel or reverse proxy.
There are two paths depending on whether you want a rebuild or not.
- Drop your
.protofile into theschemas/directory. - Open
src/server/tcp.zigand add amapTopicToSchemacall insideTcpServer.init:
// Load Schemas — already called automatically for every .proto in schemas/
try server.schema_manager.loadSchemasFromDir("schemas");
// Add your mapping:
try server.schema_manager.mapTopicToSchema("telemetry/gps", "GpsCoordinate");- Rebuild and restart:
zig build -Doptimize=ReleaseSafe --prefix /opt/protomq
sudo systemctl restart protomqThe schema directory is scanned on startup; every .proto file found there is parsed automatically — you only need to add the topic-to-message-type mapping line.
If the server was built with -Dadmin_server=true, you can register a schema at runtime without restarting:
curl -X POST http://127.0.0.1:8080/api/v1/schemas \
-H "Authorization: Bearer ${ADMIN_TOKEN:-admin_secret}" \
-H "Content-Type: application/json" \
-d '{
"topic": "telemetry/gps",
"message_type": "GpsCoordinate",
"proto_file_content": "syntax = \"proto3\";\nmessage GpsCoordinate { float lat = 1; float lon = 2; }"
}'
# → OKThe schema is parsed in-process and persisted as schemas/<MessageType>.proto on disk. The mapping is live immediately; no restart needed.
The Admin Server is off by default to keep the binary minimal for embedded targets. Enable it at compile time with a single flag:
zig build -Dadmin_server=trueWith the flag, the server binary gains an HTTP listener on 127.0.0.1:8080. Without the flag, the HTTP code is completely absent from the binary — zero overhead, not just disabled.
| Build | Binary size | Memory baseline | Admin API |
|---|---|---|---|
zig build |
smaller | ~2.6 MB | ✗ |
zig build -Dadmin_server=true |
slightly larger | ~4.0 MB | ✓ |
Available endpoints (all require Authorization: Bearer <ADMIN_TOKEN>):
| Method | Path | Description |
|---|---|---|
GET |
/metrics |
Active connections, messages routed, loaded schemas |
GET |
/api/v1/schemas |
Current topic-to-message-type mappings |
POST |
/api/v1/schemas |
Register a new schema and topic mapping at runtime |
The Admin Server runs cooperatively on the same event loop as the MQTT broker, so enabling it does not degrade per-message MQTT performance.