Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

GitHub

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.

ComponentTypeImagePurpose
mosquittoService Containereclipse-mosquitto:latestMQTT broker providing message routing and pub/sub functionality
cloudflaredService Containercloudflare/cloudflared:latestCloudflare Tunnel client establishing secure outbound connection
mosquitto.confConfiguration FileN/ADefines MQTT listener ports, protocols, and access policies
docker-compose.ymlOrchestrationN/ADefines service configurations, networking, and restart policies
.envSecrets FileN/AContains 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 .env file)
  • 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 PortProtocolConfigurationPurposeExternal Access
1883TCP MQTTlistener 1883
allow_anonymous trueStandard MQTT protocolNot exposed (container-internal only)
9001WebSocket MQTTlistener 9001
protocol websocketsMQTT 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:

  1. Hostname: mosquitto → Docker DNS resolves to mosquitto container's internal IP
  2. 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

FilePrimary ConsumerConfiguration ScopeVersion Controlled
docker-compose.ymlDocker Compose CLIService orchestration, container definitions, volumes, environment variable namesYes
mosquitto.confmosquitto containerMQTT listener configuration, protocol settings, access controlYes
.envDocker Compose (injected into containers)Secret values, particularly CLOUDFLARE_TUNNEL_TOKENNo (excluded via .gitignore)
.env.sampleDevelopers (documentation)Template showing required environment variablesYes

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:

  1. No Inbound Ports Required: The cloudflared container initiates an outbound connection to Cloudflare's network (see docker-compose.yml14), eliminating the need for inbound firewall rules
  2. Internal Service Discovery: The proxy uses Docker's internal DNS to resolve mosquitto:9001 to the appropriate container
  3. 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 restart
  • cloudflared: The tunnel client is stateless; all configuration is provided via the CLOUDFLARE_TUNNEL_TOKEN environment 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:

PhaseComponentActionDependencies
1Docker ComposeParse docker-compose.yml.env file must exist with CLOUDFLARE_TUNNEL_TOKEN
2Docker EnginePull images if not cachedInternet connectivity to Docker Hub and Cloudflare registries
3mosquitto containerStart, load /mosquitto/config/mosquitto.confmosquitto.conf must exist at mount path
4mosquitto containerInitialize listeners on ports 1883 and 9001No port conflicts on host
5cloudflared containerStart, execute tunnel command with tokenValid CLOUDFLARE_TUNNEL_TOKEN value
6cloudflared containerEstablish outbound tunnel to CloudflareInternet connectivity, valid tunnel configuration in Cloudflare dashboard
7System ReadyBoth containers running, tunnel connectedCloudflare 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