// docs / mqtt
MQTT
A single shared EMQX broker, automatically provisioned with one user per instance, with per-project topic isolation. No additional configuration to use it from a flow; for external callers, see below.
What's included
On first manager startup, BrokerService.ensureBroker() generates an
emqx.conf, writes it to data/emqx/emqx-<hash>.conf, and
creates the openflow-emqx container on the
openflow-network bridge. Containers reach the broker by hostname;
external clients reach it on the host's published ports.
Every instance gets a username/password pair generated at create time. The credentials
are stored on the instances row and injected into the container as
OPENFLOW_MQTT_USERNAME / OPENFLOW_MQTT_PASSWORD, so MQTT In
and MQTT Out nodes pick them up via env-var substitution.
Auth flow
EMQX delegates every CONNECT to the manager. On startup, the broker's authentication chain has a single source:
{
"mechanism": "password_based",
"backend": "http",
"method": "post",
"url": "http://host.docker.internal:4071/mqtt/auth"
}
The manager's /mqtt/auth route looks the username up in the
instances table, compares the password, and returns either
{ result: 'deny' } or:
{
"result": "allow",
"is_superuser": false,
"client_attrs": {
"tenant": "openflow/<projectId>/"
},
"acl": [
{ "permission": "allow", "action": "all", "topic": "#" },
{ "permission": "allow", "action": "all", "topic": "$share/#" }
]
}
client_attrs.tenant is the mountpoint EMQX uses for the connection.
Topics published or subscribed under any name are silently prefixed with that string
on the broker side. Two clients in the same project share a mountpoint and see each
other's traffic; two clients in different projects do not.
Mountpoints, not ACLs
Tenant isolation in Openflow is enforced by mountpoint, not by ACL. The ACL returned
to EMQX is deliberately permissive (topic: "#") because the mountpoint
ahead of it makes that scope project-local. This means flows can use flat topic names
(sensors/temperature) without thinking about prefixes, and migrating a
legacy MQTT flow into Openflow doesn't require rewriting topic strings.
Connecting external clients
The instance settings page surfaces an MQTT credentials block: hostname (apex or per-instance, depending on your DNS), port, username, password, and the topic prefix the instance is bound to. Use these exactly for any external client (a service, an IoT device, a bridge).
Notifier pattern
A common pattern is a small Python or Node service that listens to Postgres NOTIFY and publishes to MQTT for the flows to react to. Three things to get right:
- Use the instance's username and password (or create a dedicated notifier user scoped to a project).
- Topics will be silently prefixed with that project's mountpoint, so flows
subscribing to
db/update/#will see what your notifier publishes todb/update/<table>. - If you're using
paho-mqtt 2.xin Python, callloop_start()andinfo.wait_for_publish(timeout=...)around each publish. Without that, the network thread never drives the handshake and publishes silently fail.
Ports & TLS
| port | protocol | use |
|---|---|---|
| 1883 | plain MQTT | in-cluster / behind a private network |
| 8883 | MQTT over TLS | external clients, internet-facing |
| 8083 | MQTT over WebSocket | browser-side clients |
| 8084 | MQTT over WSS | browser-side, TLS-terminated at nginx |
| 18083 | EMQX dashboard | bound to 127.0.0.1 only; tunnel to reach |
TLS is terminated by the broker container directly on 8883 and 8084 using the wildcard cert mounted into the broker.