This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
System Architecture
Relevant source files
Purpose and Scope
This document describes the technical architecture of the Docker MQTT Mosquitto Cloudflare Tunnel system, including its component structure, network topology, data flow patterns, and configuration management. This page focuses on the structural aspects of the system. For security-specific architecture details, see Security Model.
Sources: README.md, docker-compose.yml, mosquitto.conf
System Components Overview
The system consists of two primary Docker containers orchestrated by Docker Compose, with supporting configuration files that define their behavior.
| Component | Type | Image | Purpose |
|---|---|---|---|
mosquitto | Service Container | eclipse-mosquitto:latest | MQTT broker providing message routing and pub/sub functionality |
cloudflared | Service Container | cloudflare/cloudflared:latest | Cloudflare Tunnel client establishing secure outbound connection |
mosquitto.conf | Configuration File | N/A | Defines MQTT listener ports, protocols, and access policies |
docker-compose.yml | Orchestration | N/A | Defines service configurations, networking, and restart policies |
.env | Secrets File | N/A | Contains CLOUDFLARE_TUNNEL_TOKEN for tunnel authentication |
Sources: docker-compose.yml:1-18 mosquitto.conf:1-6
Container Architecture
The following diagram illustrates the two-container architecture and their configuration dependencies:
graph TB
subgraph DockerHost["Docker Host"]
subgraph ComposeStack["Docker Compose Stack"]
MosquittoContainer["mosquitto container"]
CloudflaredContainer["cloudflared container"]
end
subgraph ConfigFiles["Configuration Files"]
ComposeYML["docker-compose.yml"]
MosquittoConf["mosquitto.conf"]
EnvFile[".env"]
end
end
subgraph ExternalDeps["External Dependencies"]
MosquittoImage["eclipse-mosquitto:latest"]
CloudflaredImage["cloudflare/cloudflared:latest"]
CloudflareNetwork["Cloudflare Edge Network"]
end
ComposeYML -->|orchestrates| MosquittoContainer
ComposeYML -->|orchestrates| CloudflaredContainer
MosquittoImage -->|provides runtime| MosquittoContainer
CloudflaredImage -->|provides runtime| CloudflaredContainer
MosquittoConf -->|volume mount ./mosquitto.conf:/mosquitto/config/mosquitto.conf| MosquittoContainer
EnvFile -->|environment variable CLOUDFLARE_TUNNEL_TOKEN| CloudflaredContainer
CloudflaredContainer -->|tunnel --no-autoupdate run --token| CloudflareNetwork
MosquittoContainer -.->|internal proxy mosquitto:9001| CloudflaredContainer
Container Service Architecture
Sources: docker-compose.yml:1-18 mosquitto.conf:1-6
Service Configuration Details
The mosquitto service is defined in docker-compose.yml:4-9 with the following characteristics:
- Container Name:
mosquitto(used for internal DNS resolution) - Image:
eclipse-mosquitto:latest - Volume Mount:
./mosquitto.conf:/mosquitto/config/mosquitto.conf(read-only configuration) - Restart Policy:
unless-stopped(automatic recovery from failures)
The cloudflared service is defined in docker-compose.yml:11-17 with the following characteristics:
- Container Name:
cloudflared - Image:
cloudflare/cloudflared:latest - Command:
tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN} - Environment Variable:
CLOUDFLARE_TUNNEL_TOKEN(sourced from.envfile) - Restart Policy:
unless-stopped
Sources: docker-compose.yml:1-18
Network Topology
The system implements a multi-layer network architecture with both internal Docker networking and external Cloudflare tunnel connectivity.
graph TB
subgraph Internet["Public Internet"]
MQTTClients["MQTT Clients"]
end
subgraph CloudflareInfra["Cloudflare Infrastructure"]
CFEdge["Cloudflare Edge Network"]
TunnelService["Tunnel Service"]
end
subgraph DockerInternal["Docker Internal Network (default)"]
subgraph CloudflaredContainer["cloudflared container"]
TunnelClient["tunnel client process"]
end
subgraph MosquittoContainer["mosquitto container"]
Listener1883["listener 1883\nTCP MQTT"]
Listener9001["listener 9001\nWebSocket MQTT"]
end
end
MQTTClients -->|MQTT/WebSocket public hostname| CFEdge
CFEdge <-->|encrypted tunnel outbound from cloudflared| TunnelService
TunnelService <-->|tunnel connection| TunnelClient
TunnelClient -->|HTTP proxy to mosquitto:9001| Listener9001
Listener1883 -.->|not exposed externally local only| DockerInternal
Listener9001 -->|internal routing| TunnelClient
Network Layer Architecture
Sources: mosquitto.conf:1-6 docker-compose.yml:11-17 README.md:56-65
Port and Protocol Configuration
The mosquitto service exposes two listener ports configured in mosquitto.conf:1-6:
| Listener Port | Protocol | Configuration | Purpose | External Access |
|---|---|---|---|---|
| 1883 | TCP MQTT | listener 1883 | ||
allow_anonymous true | Standard MQTT protocol | Not exposed (container-internal only) | ||
| 9001 | WebSocket MQTT | listener 9001 | ||
protocol websockets | MQTT over WebSockets (inherits allow_anonymous true) | Proxied via Cloudflare Tunnel |
The cloudflared service does not expose any ports directly. It establishes an outbound connection to Cloudflare's network and proxies inbound traffic to mosquitto:9001 via Docker's internal DNS resolution.
Sources: mosquitto.conf:1-6 README.md:62
Data Flow Architecture
The following diagram illustrates the complete message flow from external clients through the Cloudflare Tunnel to the Mosquitto broker.
sequenceDiagram
participant Client as "MQTT Client"
participant CFEdge as "Cloudflare Edge"
participant Cloudflared as "cloudflared container"
participant Mosquitto as "mosquitto container"
participant MosqConf as "mosquitto.conf"
Note over Cloudflared: Startup initialization
Cloudflared->>Cloudflared: Read CLOUDFLARE_TUNNEL_TOKEN from environment
Cloudflared->>CFEdge: Execute: tunnel --no-autoupdate run --token
CFEdge-->>Cloudflared: Tunnel established (outbound connection)
Note over Mosquitto: Startup initialization
Mosquitto->>MosqConf: Load /mosquitto/config/mosquitto.conf
MosqConf-->>Mosquitto: Configure listener 1883 (TCP)
MosqConf-->>Mosquitto: Configure listener 9001 (WebSocket)
MosqConf-->>Mosquitto: Set allow_anonymous true
Note over Client,Mosquitto: MQTT Connection Phase
Client->>CFEdge: MQTT CONNECT via public hostname
CFEdge->>Cloudflared: Route through tunnel
Cloudflared->>Mosquitto: HTTP proxy to mosquitto:9001
Mosquitto->>Mosquitto: Validate listener 9001 (WebSocket)
Mosquitto->>Mosquitto: Check allow_anonymous (true)
Mosquitto-->>Cloudflared: MQTT CONNACK
Cloudflared-->>CFEdge: Tunnel response
CFEdge-->>Client: MQTT CONNACK
Note over Client,Mosquitto: MQTT Publish Phase
Client->>CFEdge: MQTT PUBLISH topic/message
CFEdge->>Cloudflared: Tunnel
Cloudflared->>Mosquitto: Proxy to :9001
Mosquitto->>Mosquitto: Route to topic subscribers
Mosquitto-->>Cloudflared: MQTT PUBACK
Cloudflared-->>CFEdge: Tunnel
CFEdge-->>Client: MQTT PUBACK
MQTT Message Flow Sequence
Sources: docker-compose.yml:11-17 mosquitto.conf:1-6 README.md:15-73
Internal Service Communication
Within the Docker Compose stack, services communicate using Docker's built-in DNS resolution. The cloudflared container references the mosquitto service by its container name (mosquitto) as configured in docker-compose.yml6
When configuring the Cloudflare Tunnel's public hostname, the service URL is set to mosquitto:9001 (as described in README.md:62), which resolves to:
- Hostname:
mosquitto→ Docker DNS resolves tomosquittocontainer's internal IP - Port:
9001→ The WebSocket listener defined in mosquitto.conf:4-5
No explicit Docker network configuration is required because both services use the default bridge network created by Docker Compose.
Sources: docker-compose.yml6 mosquitto.conf:4-5 README.md:62
Configuration File Relationships
The following diagram maps configuration files to their consuming containers and the specific configuration aspects they control:
graph LR
subgraph ConfigLayer["Configuration Layer"]
ComposeFile["docker-compose.yml"]
MosqConf["mosquitto.conf"]
EnvFile[".env"]
EnvSample[".env.sample"]
end
subgraph RuntimeLayer["Runtime Layer"]
MosqContainer["mosquitto container"]
CFContainer["cloudflared container"]
DockerEngine["Docker Engine"]
end
subgraph ConfigContent["Configuration Content"]
ServiceDef["Service definitions:\nimage, container_name,\nrestart policies"]
VolumeMount["Volume mount:\n./mosquitto.conf:/mosquitto/config/mosquitto.conf"]
EnvVar["Environment variable:\nCLOUDFLARE_TUNNEL_TOKEN"]
ListenerConf["Listeners:\n1883 (TCP)\n9001 (WebSocket)"]
AnonAccess["Access control:\nallow_anonymous true"]
TunnelToken["Tunnel token:\nCLOUDFLARE_TUNNEL_TOKEN=..."]
end
EnvSample -.->|template for| EnvFile
ComposeFile --> ServiceDef
ComposeFile --> VolumeMount
ComposeFile --> EnvVar
ServiceDef -->|defines| MosqContainer
ServiceDef -->|defines| CFContainer
VolumeMount -->|mounts into| MosqContainer
EnvVar -->|injects into| CFContainer
MosqConf --> ListenerConf
MosqConf --> AnonAccess
ListenerConf -->|configures| MosqContainer
AnonAccess -->|configures| MosqContainer
EnvFile --> TunnelToken
TunnelToken -->|provides to| CFContainer
DockerEngine -->|runs| MosqContainer
DockerEngine -->|runs| CFContainer
Configuration Dependency Graph
Sources: docker-compose.yml:1-18 mosquitto.conf:1-6
Configuration File Purposes
| File | Primary Consumer | Configuration Scope | Version Controlled |
|---|---|---|---|
docker-compose.yml | Docker Compose CLI | Service orchestration, container definitions, volumes, environment variable names | Yes |
mosquitto.conf | mosquitto container | MQTT listener configuration, protocol settings, access control | Yes |
.env | Docker Compose (injected into containers) | Secret values, particularly CLOUDFLARE_TUNNEL_TOKEN | No (excluded via .gitignore) |
.env.sample | Developers (documentation) | Template showing required environment variables | Yes |
The .env file is excluded from version control (see .gitignore) to prevent accidental exposure of the CLOUDFLARE_TUNNEL_TOKEN, which provides authentication credentials for the Cloudflare Tunnel.
Sources: docker-compose.yml:1-18 mosquitto.conf:1-6 README.md:51
Architectural Patterns
Reverse Proxy via Outbound Tunnel
The system implements a reverse proxy pattern where the cloudflared container acts as a secure tunnel endpoint that proxies external traffic to the internal mosquitto service. This pattern has several architectural implications:
- No Inbound Ports Required: The
cloudflaredcontainer initiates an outbound connection to Cloudflare's network (see docker-compose.yml14), eliminating the need for inbound firewall rules - Internal Service Discovery: The proxy uses Docker's internal DNS to resolve
mosquitto:9001to the appropriate container - Protocol Translation: External MQTT clients connect via standard protocols, while the tunnel uses HTTP/HTTPS for transport
Sources: docker-compose.yml:11-17 README.md:15-73
Stateless Container Design
Both containers follow a stateless design pattern :
mosquitto: No persistent data volume is configured in docker-compose.yml:4-9 meaning broker state (retained messages, subscriptions) is ephemeral and lost on container restartcloudflared: The tunnel client is stateless; all configuration is provided via theCLOUDFLARE_TUNNEL_TOKENenvironment variable
This design prioritizes simplicity and ease of deployment over data persistence. For persistent storage requirements, see Advanced Topics.
Sources: docker-compose.yml:1-18
Service Restart Policy
Both services use restart: unless-stopped (docker-compose.yml:9-15), which provides automatic recovery from failures while allowing manual stops to persist. This policy ensures:
- Containers automatically restart after crashes or Docker daemon restarts
- Manually stopped containers remain stopped (via
docker compose stop) - System reboots trigger container restarts
Sources: docker-compose.yml:9-15
Component Lifecycle
The following table describes the startup sequence and dependencies:
| Phase | Component | Action | Dependencies |
|---|---|---|---|
| 1 | Docker Compose | Parse docker-compose.yml | .env file must exist with CLOUDFLARE_TUNNEL_TOKEN |
| 2 | Docker Engine | Pull images if not cached | Internet connectivity to Docker Hub and Cloudflare registries |
| 3 | mosquitto container | Start, load /mosquitto/config/mosquitto.conf | mosquitto.conf must exist at mount path |
| 4 | mosquitto container | Initialize listeners on ports 1883 and 9001 | No port conflicts on host |
| 5 | cloudflared container | Start, execute tunnel command with token | Valid CLOUDFLARE_TUNNEL_TOKEN value |
| 6 | cloudflared container | Establish outbound tunnel to Cloudflare | Internet connectivity, valid tunnel configuration in Cloudflare dashboard |
| 7 | System Ready | Both containers running, tunnel connected | Cloudflare public hostname configured to route to mosquitto:9001 |
There is no explicit startup ordering defined in docker-compose.yml (no depends_on directive), meaning both containers start simultaneously. However, the cloudflared container will retry tunnel connections until successful, providing implicit resilience to timing issues.
Sources: docker-compose.yml:1-18 mosquitto.conf:1-6