Deploy an MQTT Broker with Docker: Eclipse Mosquitto on a Raw TCP Port

·5 min read·Updated
mqttdockertcpiothow-to

To deploy an MQTT broker like Eclipse Mosquitto, you need three things most HTTP-first hosting platforms don't hand you out of the box: a place to mount a mosquitto.conf file, persistent storage for the message store, and a public port that speaks raw TCP instead of HTTP. On JustRunMy.App you get all three on the same app: deploy the eclipse-mosquitto Docker image, attach a config file and a volume, then expose the broker's port as TCP instead of HTTPS. No reverse proxy, no dedicated-IP add-on, no translating MQTT into WebSockets just to get it through an HTTP-only edge.

Why MQTT brokers break the typical PaaS deploy model

Most container-hosting platforms are built around one assumption: traffic is HTTP(S), so the edge can terminate TLS and route by Host header or SNI. MQTT doesn't fit that model — clients open a raw TCP connection straight to a port (1883 for plaintext, 8883 for TLS), and there's no Host header for an HTTP-shaped proxy to route on.

That's why raw TCP support is inconsistent across the market. On Fly.io, shared IPv4 addresses route by SNI or Host header, so a plaintext, non-TLS TCP service like a default Mosquitto listener won't work until you allocate a dedicated IPv4 address (Fly.io community) — an extra paid resource just to get L4 traffic through. Render's routing and health checks are built around HTTP and WebSocket services, with no equivalent raw TCP proxy in its standard docs (Render docs). Railway does offer a comparable TCP Proxy that maps a generated domain:port to your container (Railway docs) — proof that platforms can support this well, just not all of them do by default.

JustRunMy.App treats TCP and UDP as first-class port protocols alongside HTTPS on every app, so a broker, game server, or custom binary-protocol service is deployed the same way as a web app — just with a different protocol selected in the Ports panel.

What you're deploying

Four pieces, all on one app:

  1. Docker image — the public eclipse-mosquitto image, deployed like any other Docker image.
  2. Config file — a mosquitto.conf that tells Mosquitto to listen on the network and persist its store, via Config Files.
  3. Volume — persistent storage so retained messages and the message store survive restarts, via Volumes.
  4. Port — the broker's listener exposed as a raw TCP port, via Ports.

Step 1: Deploy the eclipse-mosquitto image

Open Add App → Docker Image and enter eclipse-mosquitto:2 as the image reference, then create the app. It pulls the image and starts it with the image's default command.

Mosquitto 2.x refuses non-local connections until it's given an explicit listener — the default config only binds to loopback inside the container (Mosquitto Docker docs). That's what the next step fixes.

Step 2: Add a mosquitto.conf config file

In Config Files, add a file at the container path /mosquitto/config/mosquitto.conf with contents along these lines:

listener 1883
allow_anonymous true
persistence true
persistence_location /mosquitto/data/
log_dest stdout

allow_anonymous true is fine for testing but not for anything public-facing — see Securing it for real traffic below. Saving a config file change restarts the app to apply it.

Step 3: Add a volume for persistence

In Volumes, add a volume at the container path /mosquitto/data — the same path referenced by persistence_location in the config above. With persistence true set, Mosquitto writes its retained messages and subscription state there, so they survive restarts and image updates instead of resetting every time the container restarts.

Step 4: Expose port 1883 as TCP

In Ports, add a binding with protocol TCP and container port 1883. Leave the subdomain empty to get a generated hostname, or set your own. Save — the app restarts to apply the new binding.

The public endpoint isn't necessarily port 1883 itself: the platform maps external connections to a safe high port internally, and the Ports panel shows you the effective public host and port your clients should connect to.

Connect and test

Using the host and port shown in the Ports panel:

mosquitto_sub -h <app-host> -p <external-port> -t test/topic &
mosquitto_pub -h <app-host> -p <external-port> -t test/topic -m "hello"

If the subscriber prints hello, the broker is reachable over raw TCP end to end.

Securing it for real traffic

Before pointing real devices at it:

  • Replace anonymous access with Mosquitto's own username/password file, referenced from mosquitto.conf (password_file /mosquitto/config/passwd) and uploaded the same way as the main config.
  • Add TLS if your clients need it: add a second listener 8883 block in mosquitto.conf, upload your certificate and key as additional Config Files (a documented use case for Config Files is exactly this — SSL/TLS bundles and CA certificates), and add a second TCP port binding for 8883. Ports support multiple bindings per app, so plaintext 1883 and TLS 8883 can run side by side.

FAQ

Can I run non-HTTP services on JustRunMy.App?

Yes. Every app's Ports panel supports HTTPS, TCP, and UDP protocols, so brokers, game servers, and custom binary protocols deploy the same way as a web app — you just pick a different protocol per port.

Do I need a dedicated IP address for raw TCP?

No. TCP and UDP ports are available on every app without a separate paid IP allocation, unlike platforms where non-TLS raw TCP requires a dedicated IPv4 add-on.

What port does Mosquitto listen on by default?

1883 for plaintext MQTT, 8883 for MQTT over TLS. Both are configurable via listener directives in mosquitto.conf.

How do I persist MQTT data across restarts?

Mount a volume at the path set by persistence_location in mosquitto.conf (for example /mosquitto/data) and make sure persistence true is set. Without both, the message store resets on every restart.

Does deploying from a public Docker image work the same way for other brokers or services?

Yes — any public or private registry image deploys the same way. Add config files, volumes, and ports afterward based on what that particular image needs.

Wrapping up

An MQTT broker is a good stress test for a hosting platform because it needs everything a typical web app doesn't: a raw TCP listener instead of HTTP routing, a config file instead of just env vars, and a volume that actually matters for correctness, not just convenience. Deploying Eclipse Mosquitto this way — Docker image, config file, volume, TCP port — is the same pattern you'd use for any other non-HTTP service: a game server, a custom binary protocol, or an internal queue you'd rather not expose through an HTTP-shaped edge.