// 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 to db/update/<table>.
  • If you're using paho-mqtt 2.x in Python, call loop_start() and info.wait_for_publish(timeout=...) around each publish. Without that, the network thread never drives the handshake and publishes silently fail.

Ports & TLS

portprotocoluse
1883plain MQTTin-cluster / behind a private network
8883MQTT over TLSexternal clients, internet-facing
8083MQTT over WebSocketbrowser-side clients
8084MQTT over WSSbrowser-side, TLS-terminated at nginx
18083EMQX dashboardbound 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.