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.

Overview

Loading…

Overview

Relevant source files

Purpose and Scope

This document provides a comprehensive introduction to the Docker MQTT Mosquitto with Cloudflare Tunnel system, explaining its architecture, components, and operational model. This system deploys a containerized Eclipse Mosquitto MQTT broker and securely exposes it to the internet via Cloudflare Tunnel, eliminating the need for publicly exposed ports or inbound firewall rules.

For detailed architectural diagrams and component interactions, see System Architecture. For security principles and Zero Trust implementation details, see Security Model. For step-by-step deployment instructions, see Getting Started.

Sources: README.md:1-23


What is This System?

The Docker MQTT Mosquitto with Cloudflare Tunnel system is a production-ready deployment pattern that combines:

  • Eclipse Mosquitto : An open-source MQTT message broker that facilitates publish/subscribe messaging between IoT devices and applications
  • Cloudflare Tunnel : A secure tunneling service that exposes local services to the internet without opening inbound ports
  • Docker Compose : Container orchestration for reproducible, multi-container deployments

The system implements a Zero Trust security model where the MQTT broker has no internet-facing attack surface. All external traffic is routed through Cloudflare’s global edge network, which provides DDoS protection, SSL termination, and geographic distribution.

Sources: README.md:17-23


Key Features

The system provides the following capabilities:

FeatureDescriptionImplementation
Zero Trust AccessNo publicly exposed inbound ports on origin serverCloudflare Tunnel with outbound-only connections
Protocol SupportNative MQTT and WebSocket protocolsMosquitto listeners on ports 1883 and 9001
Container OrchestrationReproducible multi-container deploymentDocker Compose with service definitions
Automated TestingService health verification on every commitGitHub Actions CI workflow
DocumentationAuto-generated and published documentationGitHub Actions with DeepWiki integration
Secret ManagementSecure credential handling excluded from version control.env file with .gitignore protection
TLS EncryptionEnd-to-end encrypted connectionsCloudflare edge network SSL termination
Global DistributionLow-latency access from any geographic regionCloudflare’s global edge network

Sources: README.md:1-23 .github/workflows/ci.yml:1-41 .github/workflows/build-docs.yml:1-84


Core Components

The system consists of three primary components orchestrated by Docker Compose:

Component Summary

ComponentContainer NamePurposeConfiguration
Mosquitto BrokermosquittoMQTT message broker handling pub/sub messagingmosquitto.conf:1-5 volume mount
Cloudflare ConnectorcloudflaredTunnel client establishing secure outbound connectionEnvironment variable CLOUDFLARE_TUNNEL_TOKEN
Docker ComposeN/AOrchestration layer managing container lifecycledocker-compose.yml:1-19

Supporting Configuration Files

FilePurposeVersion Control Status
mosquitto.confDefines MQTT listener ports and protocolsCommitted to repository
.envContains CLOUDFLARE_TUNNEL_TOKEN secretExcluded via .gitignore
.env.sampleTemplate showing required environment variablesCommitted to repository
.gitignorePrevents secret leakage to version controlCommitted to repository

Sources: docker-compose.yml:1-19 mosquitto.conf:1-5 .env.sample1 .gitignore:1-2


graph TB
    subgraph Internet["Internet / Public Network"]
Client["External MQTT Clients\n(IoT Devices, Applications)"]
end
    
    subgraph CF["Cloudflare Network"]
Edge["Cloudflare Edge\nTLS Termination\nPort 443"]
Tunnel["Cloudflare Tunnel Service"]
end
    
    subgraph DockerHost["Docker Host"]
Compose["docker-compose.yml\nServices Orchestration"]
subgraph Network["Docker Network (Bridge)"]
MosqContainer["Container: mosquitto\neclipse-mosquitto:latest\ncontainer_name: mosquitto"]
CFDContainer["Container: cloudflared\ncloudflare/cloudflared:latest\ncontainer_name: cloudflared"]
end
        
        MosqConf["mosquitto.conf\nVolume Mount\n/mosquitto/config/mosquitto.conf"]
EnvFile[".env\nCLOUDFLARE_TUNNEL_TOKEN"]
end
    
 
   Client -->|HTTPS/WSS Port 443| Edge
 
   Edge -->|Secure Tunnel| Tunnel
 
   Tunnel <-->|Outbound Connection Token Authentication| CFDContainer
    
 
   Compose -->|manages| MosqContainer
 
   Compose -->|manages| CFDContainer
    
 
   MosqConf -->|volumes: ./mosquitto.conf| MosqContainer
 
   EnvFile -->|environment: CLOUDFLARE_TUNNEL_TOKEN| CFDContainer
    
 
   CFDContainer -->|HTTP to mosquitto:9001| MosqContainer
    
 
   MosqContainer -->|listener 1883 protocol mqtt| ListenerMQTT["MQTT Protocol\nInternal Port 1883"]
MosqContainer -->|listener 9001 protocol websockets| ListenerWS["WebSocket Protocol\nInternal Port 9001"]

System Topology

The following diagram illustrates the complete system architecture, mapping high-level concepts to specific code entities and configuration elements:

Container and Network Topology

Key Code Entities:

Sources: docker-compose.yml:1-19 mosquitto.conf:1-5 README.md:64-67


sequenceDiagram
    participant Client as "External Client\n(MQTT/WebSocket)"
    participant CFEdge as "Cloudflare Edge\nPort 443 HTTPS"
    participant CFTunnel as "Cloudflare Tunnel\nInfrastructure"
    participant CloudflaredContainer as "Container: cloudflared\nCommand: tunnel run"
    participant DockerDNS as "Docker Internal DNS\nmosquitto:9001"
    participant MosquittoContainer as "Container: mosquitto\nPort 9001 Listener"
    
    Note over Client,MosquittoContainer: Initial Connection Phase
    CloudflaredContainer->>CFTunnel: Establish tunnel using\nCLOUDFLARE_TUNNEL_TOKEN
    CFTunnel-->>CloudflaredContainer: Tunnel authenticated and ready
    
    Note over Client,MosquittoContainer: Client CONNECT Request
    Client->>CFEdge: CONNECT to https://subdomain.domain:443
    CFEdge->>CFTunnel: Route via tunnel config\nPublic Hostname mapping
    CFTunnel->>CloudflaredContainer: Forward through secure tunnel
    CloudflaredContainer->>DockerDNS: Resolve "mosquitto:9001"
    DockerDNS-->>CloudflaredContainer: Return container IP
    CloudflaredContainer->>MosquittoContainer: HTTP request to port 9001\nprotocol websockets
    MosquittoContainer-->>CloudflaredContainer: WebSocket upgrade response
    CloudflaredContainer-->>CFTunnel: Return response
    CFTunnel-->>CFEdge: Return response
    CFEdge-->>Client: HTTPS/WSS connection established
    
    Note over Client,MosquittoContainer: Client PUBLISH Message
    Client->>CFEdge: PUBLISH to topic
    CFEdge->>CFTunnel: Forward
    CFTunnel->>CloudflaredContainer: Via tunnel
    CloudflaredContainer->>MosquittoContainer: HTTP POST to mosquitto:9001
    MosquittoContainer->>MosquittoContainer: Process message\nRoute to subscribers
    MosquittoContainer-->>CloudflaredContainer: PUBACK
    CloudflaredContainer-->>CFTunnel: Return
    CFTunnel-->>CFEdge: Return
    CFEdge-->>Client: PUBACK confirmation

Request Flow Architecture

This diagram traces a complete MQTT message flow from external client to broker, mapping each step to specific code entities and configuration elements:

End-to-End Message Flow

Key Code References in Flow:

  1. Tunnel Authentication : CLOUDFLARE_TUNNEL_TOKEN from docker-compose.yml:16-17 passed to cloudflared container
  2. Tunnel Command : tunnel run in docker-compose.yml13 establishes outbound connection
  3. DNS Resolution : Service name mosquitto defined in docker-compose.yml4 resolved by Docker’s internal DNS
  4. Port Configuration : 9001 listener defined in mosquitto.conf:4-5 handles WebSocket connections
  5. Protocol Setting : protocol websockets in mosquitto.conf5 enables WebSocket support
  6. Public Hostname : Configured in Cloudflare dashboard (Step 3 of README.md:57-72) maps public URL to internal mosquitto:9001

Sources: docker-compose.yml:1-19 mosquitto.conf:1-5 README.md:57-72


Deployment Model

The system uses a two-phase deployment model that separates cloud configuration from local execution:

Phase 1: Cloud Configuration (One-Time Setup)

StepActionOutcome
1Create Cloudflare Tunnel via Zero Trust dashboardGenerates unique tunnel identifier
2Configure tunnel connector type (Cloudflared)Provides Docker deployment command
3Copy CLOUDFLARE_TUNNEL_TOKEN from generated commandToken for authenticating local connector
4Configure Public Hostname mappingMaps public URL to mosquitto:9001 internal address

Sources: README.md:29-72

Phase 2: Local Deployment (Repeatable)

StepCommand/ActionFile Reference
1Create .env file from .env.sample template.env.sample1
2Add CLOUDFLARE_TUNNEL_TOKEN=<token> to .envdocker-compose.yml:16-17
3Verify .env excluded from Git.gitignore1
4Run docker compose updocker-compose.yml1

Sources: README.md:53-78 .env.sample1 .gitignore:1-2


Security Architecture

The system implements defense-in-depth security through multiple layers:

Security Layers

Key Security Implementations:

  1. No Inbound Ports : docker-compose.yml:1-19 defines no ports: sections, preventing host exposure
  2. Outbound-Only Connection : tunnel run command in docker-compose.yml13 establishes connection from origin to Cloudflare
  3. Secret Exclusion : .env pattern in .gitignore1 prevents accidental token commits
  4. Anonymous Access : Currently configured as allow_anonymous true in mosquitto.conf3 - see Authentication and Access Control for implications

For comprehensive security documentation, see Security Model.

Sources: docker-compose.yml:1-19 mosquitto.conf:1-5 .gitignore:1-2


Advanced Features

The repository includes an alternative branch with additional security features:

Protected Branch Features

The protected-no-wildcard branch implements:

FeatureImplementationDocumentation
Wildcard Topic RestrictionsACL file restricting cross-user wildcard subscriptionsREADME.md9
Encrypted Retained Messagesgocryptfs filesystem encryptionREADME.md10
Auto-Save PersistenceAutomatic message persistence after every publishREADME.md11

See Protected Branch Features for detailed documentation.

Sources: README.md:7-13


CI/CD Integration

The system includes automated quality assurance through GitHub Actions:

Continuous Integration Workflow

The .github/workflows/ci.yml:1-41 workflow performs:

  1. Service Provisioning : Starts mosquitto container via docker-compose up -d mosquitto
  2. Health Check Loop : Polls container status up to 10 times with 10-second intervals
  3. Verification : Ensures docker ps | grep mosquitto | grep "Up" succeeds
  4. Cleanup : Executes docker-compose down regardless of test outcome

Documentation Pipeline

The .github/workflows/build-docs.yml:1-84 workflow:

  1. Generates documentation using DeepWiki integration
  2. Builds mdBook static site
  3. Deploys to GitHub Pages on weekly schedule or manual trigger

For complete CI/CD documentation, see CI/CD and Automation.

Sources: .github/workflows/ci.yml:1-41 .github/workflows/build-docs.yml:1-84


Quick Start Reference

To deploy this system:

  1. Prerequisites : Docker, Docker Compose, Cloudflare account (see Prerequisites)
  2. Cloudflare Setup : Create tunnel and obtain token (see Cloudflare Tunnel Setup)
  3. Local Configuration : Create .env file with token (see Local Configuration)
  4. Deployment : Run docker compose up (see Deployment)

Your MQTT broker will be accessible at https://<subdomain>.<domain>:443 as configured in the Cloudflare Public Hostname settings.

Important : The URL mosquitto:9001 referenced in README.md64 is internal to Docker and resolved via the service name in docker-compose.yml4 The public URL is configured separately in the Cloudflare dashboard and uses HTTPS on port 443, not HTTP on port 9001.

For complete deployment instructions, see Getting Started.

Sources: README.md:25-84 docker-compose.yml:1-19

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Getting Started

Loading…

Getting Started

Relevant source files

This page guides you through the complete deployment process for the Docker MQTT Mosquitto Cloudflare Tunnel system. The setup consists of four phases: verifying prerequisites, configuring Cloudflare Tunnel, setting up local configuration files, and deploying containers.

Related Pages:

  • Prerequisites (page 2.1): System requirements and account setup
  • Cloudflare Tunnel Setup (page 2.2): Detailed tunnel creation steps
  • Local Configuration (page 2.3): Environment and configuration file setup
  • Deployment (page 2.4): Container deployment and verification

Deployment Workflow

The deployment process follows a linear sequence of configuration and initialization steps:

Diagram: Deployment Phase Sequence

stateDiagram-v2
    [*] --> Prerequisites
    Prerequisites --> CloudflareTunnel : Accounts verified
    CloudflareTunnel --> LocalConfig : CLOUDFLARE_TUNNEL_TOKEN obtained
    LocalConfig --> Deployment : .env file created
    Deployment --> [*] : Containers running
    
    state CloudflareTunnel {
        [*] --> CreateTunnel
        CreateTunnel --> ConfigureHostname
        ConfigureHostname --> [*]
    }
    
    state LocalConfig {
        [*] --> CreateEnvFile
        CreateEnvFile --> PopulateToken
        PopulateToken --> [*]
    }
    
    state Deployment {
        [*] --> DockerComposeUp
        DockerComposeUp --> VerifyContainers
        VerifyContainers --> [*]
    }

Sources : README.md:29-78

Deployment Phases Summary

PhaseKey ActionsOutput ArtifactsPage Reference
PrerequisitesInstall Docker, Docker Compose; create Cloudflare accountSystem readinessPage 2.1
Cloudflare Tunnel SetupCreate tunnel in Zero Trust dashboard; configure public hostnameCLOUDFLARE_TUNNEL_TOKENPage 2.2
Local ConfigurationCreate .env file; populate token; verify mosquitto.conf.env filePage 2.3
DeploymentExecute docker compose up; verify container statusRunning mosquitto and cloudflared containersPage 2.4

Sources : README.md:29-78 docker-compose.yml:1-18

Configuration File Flow

Diagram: File-to-Container Mapping

Sources : docker-compose.yml:1-18 .env.sample:1-3 .gitignore:1-2

Configuration File Purposes

FileVersion ControlledPurposeUsed By
docker-compose.ymlYesDefines service orchestration, container names, image references, volume mounts, environment variable injectionDocker Compose
mosquitto.confYesConfigures MQTT broker listeners on ports 1883 and 9001mosquitto container
.env.sampleYesTemplate showing required environment variablesDevelopers (documentation)
.envNoContains actual CLOUDFLARE_TUNNEL_TOKEN valuecloudflared container
.gitignoreYesPrevents .env from being committedGit

Sources : docker-compose.yml:1-18 mosquitto.conf:1-5 .env.sample:1-3 .gitignore:1-2

Quick Start Summary

This section provides an abbreviated setup sequence. For detailed instructions, see the child pages (2.1 through 2.4).

Step 1: Cloudflare Tunnel Configuration

Create a tunnel in the Cloudflare Zero Trust dashboard and obtain the CLOUDFLARE_TUNNEL_TOKEN. Configure a public hostname that routes to mosquitto:9001.

Detailed instructions : See page 2.2 (Cloudflare Tunnel Setup)

Key outputs :

  • Tunnel token string (begins with eyJ...)
  • Public hostname (e.g., mqtt.example.com)

Sources : README.md:29-73

Step 2: Local Environment Setup

Create .env file from template and populate with tunnel token:

Edit .env:

CLOUDFLARE_TUNNEL_TOKEN=<token_from_step_1>

Detailed instructions : See page 2.3 (Local Configuration)

File locations :

Sources : .env.sample:1-3 README.md53

Step 3: Container Deployment

Execute Docker Compose to start both services:

Detailed instructions : See page 2.4 (Deployment)

Services started :

Sources : README.md:76-78 docker-compose.yml:1-18

Container Initialization

Diagram: Docker Compose Service Startup Sequence

Sources : docker-compose.yml:1-18 mosquitto.conf:1-5

Post-Deployment Verification

Verify both containers are operational:

Expected output shows two running containers:

Container NameImageStatusPorts
mosquittoeclipse-mosquitto:latestUp1883/tcp, 9001/tcp
cloudflaredcloudflare/cloudflared:latestUp(no exposed ports)

Check cloudflared logs for tunnel establishment confirmation:

Check mosquitto logs for listener initialization:

Sources : docker-compose.yml:4-9 docker-compose.yml:11-17

Docker Compose Service Configuration

Diagram: Service Definition to Runtime Mapping

Sources : docker-compose.yml:1-18

Service Configuration Parameters

mosquitto Service

ParameterValuePurpose
container_namemosquittoDNS-resolvable name for internal routing
imageeclipse-mosquitto:latestOfficial Mosquitto MQTT broker image
restartunless-stoppedAutomatic restart unless explicitly stopped
volumes./mosquitto.conf:/mosquitto/config/mosquitto.confInject broker configuration

File reference : docker-compose.yml:4-9

cloudflared Service

ParameterValuePurpose
container_namecloudflaredContainer identifier for logging and management
imagecloudflare/cloudflared:latestOfficial Cloudflare Tunnel connector image
restartunless-stoppedAutomatic restart unless explicitly stopped
commandtunnel --no-autoupdate runExecute tunnel with auto-update disabled
environmentCLOUDFLARE_TUNNEL_TOKEN from .envAuthenticates tunnel connection

File reference : docker-compose.yml:11-17

Sources : docker-compose.yml:1-18

Connection Flow After Deployment

Once both containers are operational, the request flow follows this path:

  1. External clients connect to public hostname (e.g., https://mqtt.example.com:443)
  2. Cloudflare edge network receives connection
  3. Traffic routes through tunnel to cloudflared container
  4. cloudflared proxies to mosquitto:9001 via Docker internal DNS
  5. mosquitto container handles connection on WebSocket listener (port 9001)

Diagram: Client-to-Broker Request Path

graph LR
    Client["MQTT Client"]
CFEdge["Cloudflare Edge\n(Port 443)"]
Tunnel["Tunnel Infrastructure"]
CFDContainer["cloudflared container"]
MosqContainer["mosquitto container\n(Port 9001)"]
Client -->|HTTPS/WSS| CFEdge
 
   CFEdge -->|Tunnel protocol| Tunnel
 
   Tunnel -->|Encrypted tunnel| CFDContainer
 
   CFDContainer -->|HTTP to mosquitto:9001| MosqContainer

Sources : README.md:64-67 README.md82

System Management Commands

CommandActionEffect
docker compose upStart services in foregroundContainers run, logs visible in terminal
docker compose up -dStart services in backgroundContainers run detached from terminal
docker compose downStop and remove containersContainers stopped, network removed, volumes persist
docker compose restartRestart both servicesContainers restarted with existing configuration
docker logs mosquittoView mosquitto container logsDisplay broker initialization and connection logs
docker logs cloudflaredView cloudflared container logsDisplay tunnel establishment and proxy logs

Sources : docker-compose.yml:1-18

Next Steps

For detailed setup instructions , refer to child pages:

PageTitleContent
2.1PrerequisitesSystem requirements, Docker installation, Cloudflare account setup
2.2Cloudflare Tunnel SetupStep-by-step tunnel creation, hostname configuration, token extraction
2.3Local Configuration.env file creation, mosquitto.conf customization, security validation
2.4DeploymentContainer startup, health verification, troubleshooting common errors

For advanced topics , see:

  • Component details : Pages 3.1 (Mosquitto), 3.2 (Cloudflared), 3.3 (Docker Compose)
  • Configuration reference : Pages 4.1, 4.2, 4.3
  • Production deployment : Page 6.2
  • Advanced security features : Page 6.1 (see protected-no-wildcard branch for ACL and encryption)

Sources : README.md:7-13 (protected branch reference)

Common Initial Setup Issues

IssueSymptomResolution
Missing .env filecloudflared fails to start with authentication errorCreate .env file from .env.sample:1-3 template and add valid token
Invalid tunnel tokencloudflared logs show authentication failureVerify token in Cloudflare dashboard and update .env file
Port conflictsContainer fails to start with port binding errorCheck if ports 1883 or 9001 are already in use on the host
Tunnel not foundcloudflared reports tunnel does not existEnsure tunnel was created in Cloudflare dashboard and token matches
Public hostname not configuredClients cannot connectConfigure public hostname in Cloudflare dashboard as described in Step 2
Volume mount errorsmosquitto fails to load configurationVerify mosquitto.conf exists in repository root as defined in docker-compose.yml8

For comprehensive troubleshooting, see Troubleshooting.

Sources : README.md:23-73 docker-compose.yml:1-18 .env.sample:1-3

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Prerequisites

Loading…

Prerequisites

Relevant source files

This page documents the required accounts, software, system resources, and knowledge needed before deploying the Docker MQTT Mosquitto with Cloudflare Tunnel system. Complete all prerequisites in this section before proceeding to Cloudflare Tunnel Setup.

For information about the actual setup process, see Cloudflare Tunnel Setup. For details about the system’s architectural requirements, see System Architecture.


Required Accounts

Cloudflare Account with Zero Trust Access

A Cloudflare account with Zero Trust dashboard access is required to create and manage the Cloudflare Tunnel. The tunnel connector authenticates using a token generated through this interface.

Account Requirements:

  • Active Cloudflare account (free tier sufficient)
  • Access to the Zero Trust dashboard at https://one.dash.cloudflare.com/
  • Permission to create tunnels under the Networks → Tunnels section

Why Required: The system architecture depends on the Cloudflare Tunnel service to provide secure, outbound-only connectivity between the cloudflared container and Cloudflare’s edge network. Without Zero Trust access, you cannot generate the CLOUDFLARE_TUNNEL_TOKEN that authenticates the tunnel connection specified in docker-compose.yml14

Account FeatureUsage in SystemConfiguration Location
Zero Trust DashboardTunnel creation and managementN/A (external UI)
Tunnel TokenContainer authenticationdocker-compose.yml14
Public HostnameDNS routing configurationConfigured via dashboard

Sources: README.md:29-31 README.md:36-51


Required Software

Docker Engine

Docker Engine is required to run the containerized services defined in docker-compose.yml The system has been tested with Docker Engine version 20.10+ but should work with any recent release supporting Compose file format version 3.8.

Installation Verification:

Expected output format: Docker version XX.XX.X, build XXXXXXX

Why Required: The system consists of two containerized services (mosquitto and cloudflared) that must run in an isolated network environment. Docker Engine provides the container runtime, networking layer, and volume management capabilities referenced in docker-compose.yml:1-18

Docker Compose

Docker Compose orchestrates the multi-container application lifecycle. The docker-compose.yml1 file uses format version 3.8, requiring Docker Compose v1.28.0+ or Docker Engine with integrated Compose v2.

Installation Verification:

Expected output format: Docker Compose version vX.XX.X or docker-compose version X.XX.X

Why Required: The system requires coordinated startup, networking, and lifecycle management of two containers: mosquitto (MQTT broker) and cloudflared (tunnel connector). Docker Compose provides service discovery via internal DNS, allowing the cloudflared container to resolve mosquitto:9001 as configured in docker-compose.yml:4-17

Git version control is recommended for cloning the repository and tracking local configuration changes.

Installation Verification:

Why Required: While you can download the repository as a ZIP archive, Git provides version tracking for local modifications to mosquitto.conf and allows you to easily integrate updates from upstream. The .gitignore1 configuration ensures that your .env file containing secrets is never committed.

Sources: docker-compose.yml1 .gitignore1


System Requirements

Hardware Resources

The system has minimal resource requirements due to the lightweight nature of both containers.

ComponentMinimumRecommendedPurpose
CPU Cores1 core2+ coresHandle concurrent MQTT connections
RAM512 MB1 GB+Container overhead + message buffering
Disk Space500 MB2 GB+Container images + log storage
NetworkOutbound internetLow latency connectionCloudflare Tunnel connectivity

Container-Specific Resources:

The mosquitto container (docker-compose.yml:4-9) runs Eclipse Mosquitto, a minimal MQTT broker with low memory footprint. The cloudflared container (docker-compose.yml:11-17) maintains a persistent WebSocket connection to Cloudflare’s network and proxies traffic to the Mosquitto service.

Why These Requirements:

  • Mosquitto’s memory usage scales with the number of retained messages and active connections
  • The cloudflared tunnel requires stable outbound connectivity to maintain the persistent connection
  • No inbound ports are exposed, so firewall configuration is not required

Operating System

The system runs on any platform supporting Docker Engine:

  • Linux : Native Docker support (Ubuntu, Debian, RHEL, CentOS, etc.)
  • macOS : Docker Desktop for Mac
  • Windows : Docker Desktop for Windows (WSL2 backend recommended)

The containers use multi-architecture images (eclipse-mosquitto:latest and cloudflare/cloudflared:latest) that support both AMD64 and ARM64 architectures.

Sources: docker-compose.yml5 docker-compose.yml12


Domain and DNS Prerequisites

Cloudflare-Managed Domain

You must have a domain registered with and managed by Cloudflare to configure public hostname routing.

Domain Requirements:

  • Domain added to your Cloudflare account
  • Nameservers pointing to Cloudflare’s DNS servers
  • DNS zone active (orange-clouded or proxied)

Why Required: When configuring the public hostname in Cloudflare Tunnel Setup, you specify a subdomain (e.g., mqtt.example.com) that routes to your tunnel. Cloudflare must control the DNS zone to create the CNAME record pointing to the tunnel endpoint.

Diagram: DNS Resolution Flow Requiring Cloudflare-Managed Domain

graph LR
    subgraph "External DNS"
        CLIENT["Client"]
PUBLIC_DNS["Public DNS Resolver"]
end
    
    subgraph "Cloudflare Network"
        CF_DNS["Cloudflare DNS\n(Authoritative)"]
CF_TUNNEL["Cloudflare Tunnel\nInfrastructure"]
end
    
    subgraph "Local Docker Environment"
        CLOUDFLARED["cloudflared container"]
MOSQUITTO["mosquitto container"]
end
    
 
   CLIENT -->|1. DNS query: mqtt.example.com| PUBLIC_DNS
 
   PUBLIC_DNS -->|2. Query nameservers| CF_DNS
 
   CF_DNS -->|3. Return CNAME: tunnel-id.cfargotunnel.com| PUBLIC_DNS
 
   PUBLIC_DNS -->|4. Resolve to Cloudflare edge IP| CLIENT
 
   CLIENT -->|5. HTTPS request| CF_TUNNEL
 
   CF_TUNNEL <-->|6. Tunnel protocol| CLOUDFLARED
 
   CLOUDFLARED -->|7. HTTP proxy| MOSQUITTO

The diagram illustrates why domain management through Cloudflare is required. The tunnel’s public hostname must resolve to Cloudflare’s infrastructure, which requires authoritative DNS control.

Sources: README.md:61-67


File System Prerequisites

Working Directory Structure

The deployment requires a local directory containing the repository files with specific structure:

docker-mqtt-mosquitto-cloudflare-tunnel/
├── docker-compose.yml          # Service orchestration definition
├── mosquitto.conf              # Mosquitto broker configuration
├── .env                        # Secret environment variables (created by user)
├── .env.sample                 # Template for .env file
└── .gitignore                  # Ensures .env is not committed

Required Files:

User-Created Files:

  • .env: Must contain CLOUDFLARE_TUNNEL_TOKEN=<your_token> (see .env.sample1)

Protected Files:

  • .gitignore1: Ensures .env is excluded from version control

Write Permissions

The Docker Engine process requires read access to:

The Mosquitto container requires write access to its internal directories for log files and persistence (not exposed as volumes in the base configuration).

Sources: docker-compose.yml:7-8 .env.sample1 .gitignore1


graph TB
    subgraph "Local Network"
        DOCKER_HOST["Docker Host"]
CLOUDFLARED["cloudflared Container"]
end
    
    subgraph "Firewall Rules"
        OUTBOUND_443["Allow Outbound\nTCP/443\n(HTTPS)"]
INBOUND_BLOCKED["Block All Inbound\n(No ports exposed)"]
end
    
    subgraph "Cloudflare Network"
        CF_EDGE["Cloudflare Edge\nTunnel Endpoint"]
end
    
 
   CLOUDFLARED -->|Persistent WebSocket Over HTTPS| OUTBOUND_443
 
   OUTBOUND_443 --> CF_EDGE
 
   INBOUND_BLOCKED -.->|Not required| DOCKER_HOST

Network Prerequisites

Outbound Internet Access

The cloudflared container requires persistent outbound HTTPS connectivity (port 443) to Cloudflare’s network.

Required Connectivity:

Diagram: Required Network Access Pattern

Firewall Configuration:

  • Outbound: Port 443 (HTTPS) must be allowed for tunnel connectivity
  • Inbound: No inbound ports required; tunnel uses outbound-only connections
  • Internal: Docker’s default bridge network handles inter-container routing

Why This Design: The outbound-only architecture (README.md17) eliminates the need for port forwarding, dynamic DNS, or public IP addresses. The cloudflared container (docker-compose.yml:11-17) initiates the connection to Cloudflare’s network, which then proxies inbound client traffic through the established tunnel.

Sources: docker-compose.yml:11-17 README.md17


Knowledge Prerequisites

Required Technical Knowledge

Users deploying this system should have working knowledge of:

Knowledge AreaRequired ForDocumentation Reference
Docker containersUnderstanding service architectureComponents
Docker ComposeManaging multi-container deploymentsDocker Compose Orchestration
MQTT protocolConfiguring broker listeners and client connectionsMQTT Protocol Listeners
Environment variablesConfiguring secrets and runtime parametersEnvironment Variables
DNS conceptsUnderstanding public hostname routingCloudflare Tunnel Setup

Optional Advanced Knowledge

The following knowledge areas are optional but helpful for troubleshooting and customization:

  • Linux command line: For log inspection and container debugging
  • Cloudflare Zero Trust: For advanced access policies and authentication
  • MQTT ACLs: For implementing user-based topic restrictions (see [protected-no-wildcard branch](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/protected-no-wildcard branch))
  • Docker networking: For custom network configurations

Sources: README.md:7-13


Prerequisites Verification Checklist

Before proceeding to Cloudflare Tunnel Setup, verify all prerequisites:

Diagram: Prerequisites Verification Flow

After completing this checklist, proceed to Cloudflare Tunnel Setup to begin the deployment process.

Sources: README.md:29-78 docker-compose.yml:1-18


graph TB
    subgraph "Prerequisites"
        CF_ACCT["Cloudflare Account"]
DOCKER_ENG["Docker Engine"]
COMPOSE_CLI["Docker Compose"]
CF_DOMAIN["Cloudflare-Managed\nDomain"]
OUTBOUND_NET["Outbound Network\nAccess"]
end
    
    subgraph "Configuration Files"
        ENV_FILE[".env"]
ENV_SAMPLE[".env.sample"]
COMPOSE_YML["docker-compose.yml"]
MOSQ_CONF["mosquitto.conf"]
GITIGNORE[".gitignore"]
end
    
    subgraph "Runtime Artifacts"
        TOKEN_VAR["CLOUDFLARE_TUNNEL_TOKEN\nenvironment variable"]
MOSQ_SERVICE["mosquitto service"]
CFD_SERVICE["cloudflared service"]
MOSQ_MOUNT["mosquitto.conf\nvolume mount"]
end
    
 
   CF_ACCT -->|generates| TOKEN_VAR
 
   TOKEN_VAR -->|stored in| ENV_FILE
 
   ENV_SAMPLE -->|template for| ENV_FILE
    
 
   DOCKER_ENG -->|executes| COMPOSE_YML
 
   COMPOSE_CLI -->|orchestrates| COMPOSE_YML
    
 
   COMPOSE_YML -->|defines| MOSQ_SERVICE
 
   COMPOSE_YML -->|defines| CFD_SERVICE
 
   COMPOSE_YML -->|references| TOKEN_VAR
 
   COMPOSE_YML -->|mounts| MOSQ_MOUNT
    
 
   MOSQ_CONF -->|mounted as| MOSQ_MOUNT
 
   MOSQ_MOUNT -->|configures| MOSQ_SERVICE
    
 
   CF_DOMAIN -->|routes to| CFD_SERVICE
 
   OUTBOUND_NET -->|enables| CFD_SERVICE
    
 
   GITIGNORE -.->|protects| ENV_FILE

Relationship to System Files

The following diagram maps prerequisites to the specific files and configurations they enable:

Diagram: Prerequisites Mapping to System Files

This diagram shows how each prerequisite enables specific parts of the system configuration. For example:

  • The Cloudflare account generates the token stored in .env
  • Docker Engine and Compose execute docker-compose.yml:1-18
  • The mosquitto.conf:1-5 file is mounted as a volume into the mosquitto container
  • The domain and network prerequisites enable the cloudflared service to function

Sources: docker-compose.yml:1-18 mosquitto.conf:1-5 .env.sample1 .gitignore1

Dismiss

Refresh this wiki

Enter email to refresh


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

Loading…

System Architecture

Relevant source files

Purpose and Scope

This page provides a detailed technical explanation of the system architecture for the Docker MQTT Mosquitto with Cloudflare Tunnel deployment. It covers container topology, network routing, external access patterns, and configuration mechanisms. For information about security principles and threat models, see Security Model. For step-by-step deployment instructions, see Getting Started.

The architecture implements a Zero Trust access pattern where the MQTT broker remains completely unexposed to the public internet, with all external access routed through Cloudflare’s network via an outbound-only tunnel connection.

Sources: README.md:1-93


Container Topology

Service Definitions

The system consists of two containerized services orchestrated by Docker Compose:

Service NameContainer NameImagePurpose
mosquittomosquittoeclipse-mosquitto:latestMQTT message broker with WebSocket support
cloudflaredcloudflaredcloudflare/cloudflared:latestCloudflare Tunnel connector for secure external access

Both services are defined in docker-compose.yml:3-17 and run on Docker Compose’s default bridge network, enabling service-to-service communication via Docker’s internal DNS resolver.

graph TB
    subgraph DockerComposeOrchestration["Docker Compose (version 3.8)"]
subgraph MosquittoService["mosquitto service"]
MosquittoContainer["mosquitto container\n(eclipse-mosquitto:latest)"]
MosquittoVolume["Volume Mount:\n./mosquitto.conf\n→\n/mosquitto/config/mosquitto.conf"]
MosquittoRestart["restart: unless-stopped"]
end
        
        subgraph CloudflaredService["cloudflared service"]
CloudflaredContainer["cloudflared container\n(cloudflare/cloudflared:latest)"]
CloudflaredCommand["command:\ntunnel --no-autoupdate run --token"]
CloudflaredEnv["environment:\nCLOUDFLARE_TUNNEL_TOKEN"]
CloudflaredRestart["restart: unless-stopped"]
end
        
        DockerDNS["Docker Internal DNS"]
end
    
 
   MosquittoVolume -->|configures| MosquittoContainer
 
   MosquittoRestart -.->|lifecycle policy| MosquittoContainer
 
   CloudflaredEnv -->|authenticates| CloudflaredCommand
 
   CloudflaredCommand -->|runs in| CloudflaredContainer
 
   CloudflaredRestart -.->|lifecycle policy| CloudflaredContainer
    
 
   CloudflaredContainer -->|resolves 'mosquitto:9001'| DockerDNS
 
   DockerDNS -->|maps to container IP| MosquittoContainer

Container Dependency Graph

Sources: docker-compose.yml:1-18


Network Architecture

Internal Docker Networking

Docker Compose creates an isolated bridge network where containers communicate using service names as hostnames. The cloudflared service connects to the Mosquitto broker using the internal hostname mosquitto:9001, which Docker’s DNS resolver maps to the mosquitto container’s IP address on the bridge network.

Key Architectural Properties:

graph LR
    subgraph DockerBridgeNetwork["Docker Bridge Network"]
CloudflaredProc["cloudflared process\n(cloudflared container)"]
MosquittoProc["mosquitto process\n(mosquitto container)"]
CloudflaredProc -->|HTTP request to mosquitto:9001| DockerInternalDNS["Docker DNS Resolver"]
DockerInternalDNS -->|resolves to container IP| MosquittoProc
        
        subgraph MosquittoListeners["Mosquitto Listeners"]
Port1883["Port 1883\n(MQTT protocol)"]
Port9001["Port 9001\n(WebSocket/HTTP)"]
end
        
 
       MosquittoProc -->|binds| Port1883
 
       MosquittoProc -->|binds| Port9001
    end
    
    HostMachine["Host Machine\n(NO port exposure)"]
DockerBridgeNetwork -.->|isolated from| HostMachine
  • No Port Exposure: Neither service exposes ports to the host machine via the ports directive in docker-compose.yml
  • Service Discovery: The cloudflared container resolves mosquitto via Docker’s internal DNS without IP address configuration
  • Network Isolation: All inter-service communication occurs within the Docker bridge network

Sources: docker-compose.yml:4-17 README.md:64-67


External Access Flow

Cloudflare Tunnel Architecture

External clients connect to the MQTT broker through Cloudflare’s infrastructure, never directly accessing the origin server. This inverted connection model eliminates the need for inbound firewall rules or public IP addresses.

sequenceDiagram
    participant ExtClient as "External MQTT Client"
    participant CFEdge as "Cloudflare Edge Network\n(Global PoPs)"
    participant CFTunnel as "Cloudflare Tunnel Infrastructure"
    participant CloudflaredContainer as "cloudflared container"
    participant MosquittoContainer as "mosquitto container"
    
    Note over CloudflaredContainer,CFTunnel: Tunnel Establishment (Startup)
    CloudflaredContainer->>CFTunnel: Outbound connection with token
    CFTunnel-->>CloudflaredContainer: Tunnel established
    
    Note over ExtClient,MosquittoContainer: Client Connection Flow
    ExtClient->>CFEdge: Connect to subdomain.domain.com:443
    CFEdge->>CFTunnel: Route via tunnel infrastructure
    CFTunnel->>CloudflaredContainer: Forward through encrypted tunnel
    CloudflaredContainer->>MosquittoContainer: HTTP request to mosquitto:9001
    MosquittoContainer-->>CloudflaredContainer: HTTP response
    CloudflaredContainer-->>CFTunnel: Return
    CFTunnel-->>CFEdge: Return
    CFEdge-->>ExtClient: HTTPS response on port 443
    
    Note over CloudflaredContainer,MosquittoContainer: Protocol Transformation\nExternal: HTTPS/WSS (port 443)\nInternal: HTTP/WS (port 9001)

Access Pattern

  1. Outbound-Only Connection: The cloudflared container initiates a persistent outbound connection to Cloudflare’s network using the command tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN} docker-compose.yml14
  2. Token Authentication: The CLOUDFLARE_TUNNEL_TOKEN environment variable docker-compose.yml17 authenticates the tunnel to Cloudflare’s infrastructure
  3. Protocol Transformation: Cloudflare receives external HTTPS/WSS connections on port 443 and forwards them as HTTP to mosquitto:9001 README.md:63-67
  4. DNS Routing: The public hostname configured in Cloudflare’s dashboard (e.g., subdomain.domain.com) routes to the tunnel, which forwards to the internal mosquitto:9001 service endpoint

Sources: docker-compose.yml:11-17 README.md:57-82


Configuration Mechanisms

Configuration Injection Points

The system employs two distinct configuration injection mechanisms:

Configuration Details

ComponentMechanismFile PathContainer PathPurpose
cloudflaredEnvironment Variable.env (host)CLOUDFLARE_TUNNEL_TOKEN env varTunnel authentication token
mosquittoVolume Mount./mosquitto.conf (host)/mosquitto/config/mosquitto.confBroker listener and protocol configuration

Token Injection: The CLOUDFLARE_TUNNEL_TOKEN is read from the .env file on the host system docker-compose.yml17 and passed to the cloudflared container as an environment variable. The token is referenced in the container’s command via shell variable substitution: --token ${CLOUDFLARE_TUNNEL_TOKEN} docker-compose.yml14

Configuration File Mounting: The mosquitto.conf file is mounted from the host filesystem into the container at /mosquitto/config/mosquitto.conf docker-compose.yml8 This allows the Mosquitto process to read its configuration without rebuilding the container image.

Sources: docker-compose.yml:7-17 README.md53


Service Restart Policies

Both services implement the restart: unless-stopped policy docker-compose.yml:9-15 which provides automatic recovery from failures:

  • Automatic Restart: Containers restart automatically if they crash or exit unexpectedly
  • Manual Control: Containers do not restart if explicitly stopped by docker-compose stop or docker-compose down
  • System Reboot: Containers automatically start when the Docker daemon starts (unless manually stopped)

This policy ensures high availability while allowing manual intervention for maintenance operations.

Sources: docker-compose.yml:9-15


Container Lifecycle

Sources: docker-compose.yml:1-18


Communication Protocol Stack

The system implements a multi-layer protocol transformation:

LayerExternal (Internet)Internal (Docker)
TransportTLS 1.3 (port 443)Unencrypted HTTP (port 9001)
ApplicationWebSocket Secure (WSS) or MQTT over TLSWebSocket (WS) or MQTT
NetworkPublic internet via CloudflareDocker bridge network
RoutingDNS to Cloudflare Edge PoPsDocker DNS to container IP

Security Boundary: TLS termination occurs at Cloudflare’s edge network. The internal Docker network segment is trusted and does not require encryption, as it is isolated from external access.

Sources: README.md:63-82

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Cloudflare Tunnel Setup

Loading…

Cloudflare Tunnel Configuration

Relevant source files

Purpose and Scope

This page provides a detailed walkthrough of setting up a Cloudflare Tunnel through the Cloudflare Zero Trust dashboard, obtaining the tunnel token, and configuring public hostnames to route traffic to the mosquitto MQTT broker. This configuration is required before deploying the system via docker-compose.

For information about the overall system architecture and security model, see System Architecture and Security Model. For details about configuring the mosquitto broker itself, see Mosquitto Configuration. For environment variable setup, see Environment Variables.

Cloudflare Tunnel Architecture

The following diagram illustrates how the Cloudflare Tunnel configuration integrates with the codebase:

Cloudflare Tunnel Configuration Flow

graph TB
    subgraph "Cloudflare Zero Trust Dashboard"
        ZT["Zero Trust Portal"]
CreateTunnel["Create Tunnel Action"]
TunnelConfig["Tunnel Configuration\nName: user-defined"]
TokenGen["Token Generator"]
HostnameConfig["Public Hostname Config\nService: HTTP\nURL: mosquitto:9001"]
end
    
    subgraph "Local Environment"
        EnvFile[".env file"]
TokenVar["CLOUDFLARE_TUNNEL_TOKEN"]
end
    
    subgraph "docker-compose.yml"
        CloudflaredService["cloudflared service"]
Command["command: tunnel --no-autoupdate run --token"]
EnvVarRef["environment:\n- CLOUDFLARE_TUNNEL_TOKEN"]
MosquittoRef["mosquitto:9001\ncontainer_name: mosquitto"]
end
    
    subgraph "Runtime"
        CloudflaredContainer["cloudflared container"]
CFNetwork["Cloudflare Edge Network"]
MosquittoContainer["mosquitto container\nport 9001"]
end
    
 
   ZT --> CreateTunnel
 
   CreateTunnel --> TunnelConfig
 
   TunnelConfig --> TokenGen
 
   TokenGen -->|Copy token| TokenVar
 
   TunnelConfig --> HostnameConfig
    
 
   TokenVar -->|Store in| EnvFile
 
   EnvFile -->|Provides value to| EnvVarRef
    
 
   EnvVarRef --> Command
 
   Command -->|Runs in| CloudflaredService
 
   CloudflaredService -->|Creates| CloudflaredContainer
    
 
   HostnameConfig -->|Routes traffic to| MosquittoRef
 
   MosquittoRef -->|Resolves to| MosquittoContainer
    
 
   CloudflaredContainer -->|Authenticates with| CFNetwork
 
   CloudflaredContainer -->|Proxies traffic to| MosquittoContainer

Sources: README.md:23-67, docker-compose.yml:11-17, .env.sample:1

Configuration Steps

Step 1: Access Cloudflare Zero Trust Dashboard

Navigate to the Cloudflare Zero Trust portal to begin tunnel configuration. This interface manages all Cloudflare Tunnel settings.

ActionLocationDetails
Log inCloudflare DashboardUse your Cloudflare account credentials
NavigateLeft sidebarSelect “Zero Trust” option

Sources: README.md:27-29

Step 2: Create a Tunnel

The tunnel creation process generates a unique identifier and authentication token for your deployment.

StepActionConfiguration
1Navigate to Networks → TunnelsAccess tunnel management interface
2Click “Create a tunnel”Initialize tunnel creation workflow
3Select tunnel typeChoose “Cloudflared” connector type
4Name the tunnelEnter descriptive name (e.g., my_tunnel_name)
5Save tunnelFinalize tunnel creation

Tunnel Creation Process

Sources: README.md:33-44

Step 3: Extract and Store the Tunnel Token

After tunnel creation, the dashboard displays a Docker command containing the CLOUDFLARE_TUNNEL_TOKEN. This token must be stored in the .env file for use by the cloudflared service.

Token Extraction and Storage

graph LR
    subgraph "Cloudflare Dashboard"
        DockerCmd["Docker command display\nEnvironment: Docker"]
TokenInCmd["Token embedded in command"]
end
    
    subgraph "Local Repository"
        EnvSample[".env.sample\nCLOUDFLARE_TUNNEL_TOKEN=your_token"]
EnvFile[".env\nCLOUDFLARE_TUNNEL_TOKEN=actual_token"]
end
    
    subgraph "docker-compose.yml:16-17"
        EnvDeclaration["environment:\n- CLOUDFLARE_TUNNEL_TOKEN"]
end
    
 
   DockerCmd --> TokenInCmd
 
   TokenInCmd -->|Copy token value| EnvFile
 
   EnvSample -.->|Template for| EnvFile
 
   EnvFile -->|Provides variable to| EnvDeclaration
ActionFileFormat
Copy token from dashboardN/ALong alphanumeric string from Docker command
Create .env file.env (root directory)CLOUDFLARE_TUNNEL_TOKEN=<your_token>
Reference template.env.sample:1Shows required format

Important: Do not execute the Docker command displayed in the dashboard. The docker-compose.yml:11-17 configuration in this repository replaces that step by running the cloudflared container with the token from the .env file.

Sources: README.md:47-53, .env.sample:1, docker-compose.yml:16-17

Step 4: Configure Public Hostname

The public hostname configuration routes external traffic through the Cloudflare edge network to the internal mosquitto service.

Hostname Configuration Parameters

ParameterValueDescription
Public hostnameUser-defined subdomain and domainExternal URL for MQTT clients to connect
Service typeHTTPProtocol for tunnel transport (not MQTT protocol)
URLmosquitto:9001Internal Docker service reference

Important Implementation Details:

  • The URL mosquitto:9001 uses Docker’s internal DNS resolution
  • mosquitto resolves to the container with container_name: mosquitto from docker-compose.yml6
  • Port 9001 corresponds to the WebSocket listener configured in mosquitto.conf
  • The service type HTTP refers to the tunnel transport protocol; MQTT over WebSocket is carried within HTTP

Public Hostname Routing Flow

graph LR
    subgraph "Public Internet"
        Client["MQTT Client"]
PublicHostname["public-hostname.domain.com"]
end
    
    subgraph "Cloudflare Network"
        CFEdge["Cloudflare Edge"]
TunnelRoute["Tunnel Route\nService: HTTP\nURL: mosquitto:9001"]
end
    
    subgraph "Docker Network"
        CloudflaredContainer["cloudflared container\ncontainer_name: cloudflared"]
DNSResolution["Docker DNS\nmosquitto → 172.x.x.x"]
MosquittoContainer["mosquitto container\ncontainer_name: mosquitto\nport 9001 listener"]
end
    
 
   Client -->|Connect to| PublicHostname
 
   PublicHostname -->|Resolves to| CFEdge
 
   CFEdge -->|Routes via| TunnelRoute
 
   TunnelRoute -->|Proxies to| CloudflaredContainer
 
   CloudflaredContainer -->|Resolves| DNSResolution
 
   DNSResolution -->|Forwards to| MosquittoContainer

Configuration Steps:

  1. Navigate to the newly created tunnel in the Zero Trust dashboard
  2. Click “Next” to proceed to hostname configuration
  3. Fill in the public hostname configuration form:
    • Enter your desired subdomain and domain
    • Select HTTP as the service type
    • Enter mosquitto:9001 as the URL
  4. Click “Save hostname” to finalize

Sources: README.md:55-66, docker-compose.yml:4-9

Token Integration with docker-compose

The CLOUDFLARE_TUNNEL_TOKEN environment variable is consumed by the cloudflared service definition in docker-compose.yml:11-17:

cloudflared Service Configuration

Configuration ElementValuePurpose
imagecloudflare/cloudflared:latestOfficial Cloudflare tunnel client
container_namecloudflaredDNS-resolvable name within Docker network
commandtunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}Runs tunnel with token from environment
environment- CLOUDFLARE_TUNNEL_TOKENPasses variable from .env to container
restartunless-stoppedEnsures tunnel reconnects after failures

Environment Variable Flow

Sources: docker-compose.yml:11-17, .env.sample:1

Configuration Verification

After completing the configuration steps, verify the setup before deployment:

Pre-Deployment Checklist

ItemLocationVerification Method
Tunnel created in dashboardCloudflare Zero Trust → Networks → TunnelsTunnel appears in list with “Inactive” status
Public hostname configuredTunnel details → Public Hostname tabHostname entry shows mosquitto:9001 as URL
.env file existsRepository root directoryFile contains CLOUDFLARE_TUNNEL_TOKEN=<token>
Token format valid.env:1Token is long alphanumeric string (typically 150+ characters)
docker-compose.yml unchangedRepository root directoryFile matches repository version

Expected State Before Deployment:

  • Tunnel status in Cloudflare dashboard: “Inactive” (no connector running yet)
  • .env file: Contains valid token, not tracked by git (see .gitignore)
  • Public hostname: Configured but not yet accessible (no traffic can flow until containers start)

The next step is to deploy the system using docker compose up, covered in Deployment.

Sources: README.md:23-73, docker-compose.yml:1-18

Common Configuration Issues

IssueSymptomSolution
Invalid token formatcloudflared container exits immediatelyVerify token copied completely from dashboard, check for extra spaces or newlines
Public hostname not resolvingDNS lookup fails for public hostnameCheck DNS propagation, verify domain is active in Cloudflare
Wrong service URLcloudflared starts but clients cannot connectEnsure URL is mosquitto:9001, not localhost:9001 or IP address
Service type misconfigurationConnection errors despite tunnel activeService type must be HTTP, not TCP or other protocols
Token in version controlSecurity warning in git statusVerify .env is listed in .gitignore never commit .env file

Sources: docker-compose.yml:11-17, README.md:47-67

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Security Model

Loading…

Security Model

Relevant source files

Purpose and Scope

This document explains the security architecture of the Docker MQTT Mosquitto Cloudflare Tunnel system, including threat model, attack surface reduction, secret management, and network isolation mechanisms. It covers the security principles embedded in the system design and the trade-offs involved in the default configuration.

For operational deployment considerations including production hardening and monitoring, see Production Deployment Considerations. For authentication and access control configuration specifics, see Authentication and Access Control.


Core Security Principles

The system implements three foundational security principles:

PrincipleImplementationBenefit
Zero Trust Network AccessCloudflare Tunnel with outbound-only connectionsOrigin server has no exposed attack surface
Separation of Code and Secrets.gitignore exclusion of .env filePrevents credential leakage through version control
Defense in DepthMultiple isolation layers (Cloudflare Edge, Docker network, container boundaries)Compromising one layer does not expose entire system

Zero Trust Architecture

The system inverts the traditional client-server model. Instead of the MQTT broker accepting inbound connections from the internet, the cloudflared container establishes a persistent outbound connection to Cloudflare’s network. External clients never connect directly to the origin server.

Sources : README.md:17, README.md:22, README.md:67


Attack Surface Analysis

Traditional MQTT Deployment vs. Cloudflare Tunnel Deployment

Eliminated Attack Vectors

The Cloudflare Tunnel architecture eliminates entire categories of attacks:

Attack TypeTraditional DeploymentTunnel Deployment
Port ScanningVulnerable - ports 1883 and 9001 discoverableImmune - no listening ports on public internet
Direct DDoSVulnerable - origin server is attack targetProtected - Cloudflare absorbs attack
SSL/TLS Certificate ManagementRequired - operator manages certificatesUnnecessary - Cloudflare manages certificates
IP-based Access ControlFragile - IP addresses change, easily spoofedN/A - origin has no public IP exposure
Zero-day Exploits in MosquittoHigh impact - direct exploitationReduced impact - traffic filtered through Cloudflare WAF

Sources : README.md:17, Diagram 1 (High-Level System Architecture), Diagram 3 (External Access and Security Flow)


Secret Management Model

Token-Based Authentication

The CLOUDFLARE_TUNNEL_TOKEN serves as the cryptographic secret that authenticates the cloudflared container to Cloudflare’s network. This token grants the right to route traffic for the configured tunnel.

stateDiagram-v2
    [*] --> Generated : User creates tunnel in Cloudflare Dashboard
    Generated --> Copied : Token displayed in UI
    Copied --> Local : User creates .env file
    
    state Local {
        [*] --> FileCreated
        FileCreated --> TokenAdded : Paste CLOUDFLARE_TUNNEL_TOKEN=...
        TokenAdded --> GitignoreProtected : .gitignore prevents commit
    }
    
    Local --> Runtime : docker-compose reads .env
    
    state Runtime {
        [*] --> EnvLoaded
        EnvLoaded --> ContainerStarted : Environment variable injected
        ContainerStarted --> TunnelAuth : cloudflared authenticates
    }
    
    Runtime --> Operational : Tunnel established
    
    state GitignoreProtected {
        [*] --> CheckGitStatus
        CheckGitStatus --> NotTracked : .env excluded
        NotTracked --> [*]
    }
    
    Operational --> Rotated : Token rotation (manual)
    Rotated --> Generated
    
    note right of GitignoreProtected
        Critical security boundary:
        .env file must never be
        committed to version control
    end note

Secret Lifecycle

Version Control Protection

The repository implements multiple layers of protection against credential leakage:

Protection LayerImplementationFile Reference
Explicit exclusion.gitignore contains .env.gitignore1
Template file.env.sample shows format without secrets.env.sample1
DocumentationREADME instructs users to create .env manuallyREADME.md53

The .gitignore file explicitly excludes the .env file containing the tunnel token:

.env
data/*

The .env.sample template provides structure without exposing actual credentials:

CLOUDFLARE_TUNNEL_TOKEN=your_token

Important : The .env.sample file is committed to version control as documentation. Operators must create their own .env file locally and populate it with their actual token.

Sources : .gitignore:1-2 .env.sample1 README.md53


Network Security Boundaries

Container Network Isolation

Security Boundaries Defined

BoundaryTrust LevelEntry RequirementsEnforcement Mechanism
Public Internet → Cloudflare EdgeUntrusted → TrustedTLS handshake, valid hostnameCloudflare network infrastructure
Cloudflare Edge → Tunnel InfrastructureTrusted → TrustedValid tunnel configurationCloudflare internal routing
Tunnel Infrastructure → cloudflared containerTrusted → TrustedCLOUDFLARE_TUNNEL_TOKEN authenticationTunnel protocol cryptographic verification
cloudflared container → mosquitto containerTrusted → TrustedDocker internal DNS resolutionDocker bridge network isolation
External clients → mosquitto containerForbiddenN/A - no direct route existsDocker network isolation, no port exposure

Port Exposure Analysis

The docker-compose.yml configuration deliberately omits port mappings to the host system. Neither container exposes any ports outside the Docker bridge network:

This architectural decision ensures that:

  1. The Mosquitto broker is not accessible via the host’s network interfaces
  2. Port scanning of the host reveals no MQTT services
  3. Firewall rules on the host are irrelevant to MQTT access
  4. The only route to the broker is through the authenticated Cloudflare Tunnel

Sources : docker-compose.yml:1-19 mosquitto.conf:1-5 Diagram 2 (Docker Container Network Topology)


Authentication and Authorization

Default Configuration: Anonymous Access Allowed

The default mosquitto.conf configuration permits anonymous connections and does not enforce authentication:

listener 1883
protocol mqtt

listener 9001
protocol websockets

Security Implication : Any client that can reach the broker (via the Cloudflare Tunnel) can publish and subscribe to any topic without authentication.

graph LR
    Client["Client with URL"]
subgraph CloudflareLayer["Cloudflare Layer - Transport Security"]
TLS["TLS Termination\nCertificate validation"]
WAF["Web Application Firewall\nOptional rules"]
end
    
    subgraph MQTTLayer["MQTT Layer - Application Security"]
Anonymous["Anonymous access permitted\nNo username/password required"]
NoACL["No topic-level restrictions\nFull pub/sub access"]
end
    
 
   Client -->|HTTPS connection| TLS
 
   TLS -->|Decrypted, inspected| WAF
 
   WAF -->|Forwarded if allowed| Anonymous
 
   Anonymous -->|All operations permitted| NoACL
    
 
   NoACL -->|Can publish to any topic| Topics["All MQTT Topics"]
NoACL -->|Can subscribe to any topic| Topics

Access Control Model

Security Trade-offs

Security FeatureDefault ConfigurationSecurity BenefitOperational Complexity
Anonymous accessEnabledNone - reduces securityLow - no credential management
Password authenticationDisabledWould prevent unauthorized accessMedium - requires credential distribution
Topic-level ACLsNot configuredWould limit topic access per clientHigh - requires ACL file management
TLS client certificatesNot configuredWould provide mutual TLS authenticationVery High - PKI infrastructure required

Enhanced Security Branch

The repository includes an alternate configuration branch (protected-no-wildcard) that implements:

  1. Topic-based Access Control : ACL file restricts wildcard subscriptions and enforces username-based topic prefixes
  2. Encrypted Retained Messages : Uses gocryptfs to encrypt persistent message storage
  3. Auto-save retained messages : Ensures message persistence

This branch demonstrates production-grade security hardening. See Protected Branch Features for details.

Sources : mosquitto.conf:1-5 README.md:7-13


TLS and Encryption

Multi-layer Encryption Architecture

Encryption Scope

Network SegmentEncryption StatusJustification
Client → Cloudflare EdgeEncrypted (TLS 1.3)Public internet traversal requires encryption
Cloudflare Edge → Tunnel InfrastructureEncrypted (Cloudflare proprietary)Cloudflare’s internal network, encrypted by default
Tunnel Infrastructure → cloudflaredEncrypted (Tunnel protocol)Traverses internet between Cloudflare PoP and origin
cloudflaredmosquittoUnencrypted (HTTP)Isolated Docker bridge network, no external exposure

Rationale for unencrypted internal segment : The traffic between cloudflared and mosquitto occurs entirely within the Docker bridge network, which is isolated from external networks. Adding TLS to this segment would:

  • Increase CPU overhead without security benefit
  • Require certificate management for internal services
  • Complicate debugging and troubleshooting

Sources : README.md:67-68 docker-compose.yml13 Diagram 3 (External Access and Security Flow)


Operational Security Considerations

Token Rotation

The CLOUDFLARE_TUNNEL_TOKEN does not automatically expire. Best practices for production deployments:

  1. Periodic rotation : Rotate tokens on a scheduled basis (e.g., quarterly)
  2. Incident response : Immediately rotate if token exposure is suspected
  3. Access logging : Monitor Cloudflare logs for tunnel usage patterns

Secret Storage in Production

The .env file approach is suitable for development but should be replaced in production:

EnvironmentRecommended Secret StorageRationale
Development.env file (local)Simple, adequate for local testing
Production (single host)Docker secrets, HashiCorp VaultEncrypted at rest, access logging
Production (orchestrated)Kubernetes secrets, AWS Secrets ManagerIntegrated with orchestration platform

Container Image Security

Both containers use official images from trusted registries:

  • eclipse-mosquitto:latest - Official Eclipse Foundation image
  • cloudflare/cloudflared:latest - Official Cloudflare image

Recommendation : Pin specific image versions rather than using latest to ensure reproducible deployments and controlled updates:

Sources : docker-compose.yml:3-4 docker-compose.yml:11-12


Threat Model Summary

Mitigated Threats

ThreatMitigationEffectiveness
Network reconnaissanceNo exposed portsComplete - port scanning reveals nothing
Direct exploitation of MosquittoTraffic filtered through CloudflareHigh - reduces exploit surface
DDoS against originCloudflare absorbs attackVery High - Cloudflare’s network capacity
Man-in-the-middleEnd-to-end TLS encryptionHigh - certificate validation required
Credential leakage via Git.gitignore exclusionHigh - if properly configured

Residual Risks

RiskSeverityMitigation Strategy
Token compromiseHighToken rotation, access monitoring, secrets management
Anonymous MQTT accessMedium-HighEnable authentication, implement ACLs (see Protected Branch)
Cloudflare service dependencyMediumAccept as operational requirement, or implement alternative tunnel
Container escapeLowUse updated Docker versions, implement AppArmor/SELinux profiles
Insider threat (access to .env)MediumImplement principle of least privilege, audit file access

Defense-in-Depth Layers

The system implements multiple independent security layers. An attacker must bypass all layers to compromise the system:

  1. Layer 1 - Cloudflare Edge : DDoS protection, rate limiting, WAF rules
  2. Layer 2 - Tunnel Authentication : Valid tunnel token required
  3. Layer 3 - Network Isolation : No direct route to containers from external networks
  4. Layer 4 - Application Security : Mosquitto’s internal security (minimal in default config)
  5. Layer 5 - Secret Management : .gitignore prevents token leakage through version control

Sources : .gitignore:1-2 README.md82 Diagram 1 (High-Level System Architecture), Diagram 6 (Data and Secret Flow)

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Local Configuration

Loading…

Local Configuration

Relevant source files

Purpose and Scope

This document guides you through setting up the local configuration files required to deploy the Docker MQTT Mosquitto with Cloudflare Tunnel system. This includes creating the environment variable file (.env) and understanding the Mosquitto broker configuration (mosquitto.conf).

This page assumes you have already completed the Cloudflare Tunnel Setup and obtained your CLOUDFLARE_TUNNEL_TOKEN. For deployment instructions after configuration is complete, see Deployment.


Configuration Files Overview

The system requires two primary configuration files:

FilePurposeVersion ControlSecurity Level
.envContains CLOUDFLARE_TUNNEL_TOKEN secretExcluded (via .gitignore)Critical - Never commit
.env.sampleTemplate showing required variablesCommitted to repositorySafe - Contains no secrets
mosquitto.confMQTT broker listener configurationCommitted to repositorySafe - Contains no secrets
.gitignorePrevents accidental secret commitsCommitted to repositorySafety mechanism

Diagram: Configuration File Relationships

Sources: .env.sample1 .gitignore:1-2 mosquitto.conf:1-6


Environment Variable Configuration

Creating the .env File

The .env file stores sensitive configuration that must never be committed to version control. The repository includes .env.sample1 as a template.

Step 1: Copy the template

Step 2: Populate the token

Edit the .env file and replace your_token with the actual CLOUDFLARE_TUNNEL_TOKEN obtained from the Cloudflare dashboard during Cloudflare Tunnel Setup:

CLOUDFLARE_TUNNEL_TOKEN=eyJhIjoiYWJjMTIzLi4uLnJlYWxfdG9rZW5faGVyZSJ9

Environment Variable Reference

VariableRequiredDescriptionSource
CLOUDFLARE_TUNNEL_TOKENYesAuthentication token for cloudflared containerCloudflare Zero Trust dashboard

Diagram: Environment Variable Flow to Containers

Sources: .env.sample1

Security Verification

The .gitignore1 file explicitly excludes .env from version control:

.env
data/*

Verify.env is excluded:

If .env appears in git status output, verify that .gitignore1 is present and contains the correct exclusion rule.

Sources: .gitignore:1-2


Mosquitto Broker Configuration

The mosquitto.conf file configures the Eclipse Mosquitto MQTT broker. This file is committed to version control as it contains no secrets.

Listener Configuration

The mosquitto.conf:1-6 file defines two protocol listeners:

Diagram: Mosquitto Listener Configuration

graph TB
    mosq_conf["mosquitto.conf"]
listener_1883["listener 1883\nStandard MQTT protocol"]
listener_9001["listener 9001\nWebSocket protocol"]
allow_anon["allow_anonymous true\nNo authentication required"]
mosq_conf -->|Line 1| listener_1883
 
   mosq_conf -->|Line 2| allow_anon
 
   mosq_conf -->|Lines 4-5| listener_9001
    
 
   listener_1883 -.->|Used by| mqtt_clients["Native MQTT clients\nIoT devices"]
listener_9001 -.->|Used by| ws_clients["WebSocket clients\nBrowsers, web apps"]

Sources: mosquitto.conf:1-6

Configuration Directives

DirectiveValuePurposeLine
listener1883Standard MQTT protocol port (TCP)mosquitto.conf1
allow_anonymoustruePermits unauthenticated connectionsmosquitto.conf2
listener9001WebSocket protocol port (HTTP/WS)mosquitto.conf4
protocolwebsocketsEnable WebSocket framing on port 9001mosquitto.conf5

Protocol Explanation

Port 1883: Native MQTT

  • Binary MQTT protocol (MQTT v3.1.1 / v5.0)
  • Used by: IoT devices, MQTT client libraries, native applications
  • Routed through Cloudflare Tunnel as-is (protocol preservation)

Port 9001: MQTT over WebSockets

  • MQTT protocol encapsulated in WebSocket frames
  • Used by: Browser-based clients, web applications
  • Required for environments without raw TCP socket support
  • Cloudflare routes this as standard HTTP/WebSocket traffic

Diagram: Configuration File to Container Mount

graph LR
    host_fs["Host Filesystem\n./mosquitto.conf"]
compose_volume["docker-compose.yml\nvolumes:\n./mosquitto.conf:/mosquitto/config/mosquitto.conf"]
container_fs["mosquitto container\n/mosquitto/config/mosquitto.conf"]
mosq_process["Mosquitto process\nreads config on startup"]
host_fs -->|1. Volume mount defined| compose_volume
 
   compose_volume -->|2. Bind mounted| container_fs
 
   container_fs -->|3. Loaded by| mosq_process
 
   mosq_process -->|4. Opens listeners| ports["Port 1883\nPort 9001"]

Sources: mosquitto.conf:1-6


graph TB
    subgraph "Host System"
        env_file[".env\nCLOUDFLARE_TUNNEL_TOKEN=..."]
mosq_conf["mosquitto.conf\nlistener 1883\nlistener 9001"]
end
    
    subgraph "docker-compose.yml"
        compose_env["environment:\nCLOUDFLARE_TUNNEL_TOKEN"]
compose_volume["volumes:\n./mosquitto.conf:/mosquitto/config/mosquitto.conf"]
end
    
    subgraph "Containers"
        cloudflared_container["cloudflared\nEnvironment: CLOUDFLARE_TUNNEL_TOKEN\nCommand: tunnel run"]
mosquitto_container["mosquitto\nMounted: /mosquitto/config/mosquitto.conf\nReads config on startup"]
end
    
 
   env_file -->|Loaded by Docker Compose| compose_env
 
   mosq_conf -->|Volume definition| compose_volume
    
 
   compose_env -->|Injected as env var| cloudflared_container
 
   compose_volume -->|Bind mounted| mosquitto_container
    
 
   cloudflared_container -->|Authenticates with| cf_network["Cloudflare Network"]
mosquitto_container -->|Opens listeners| listeners["1883: MQTT\n9001: WebSocket"]

Configuration Flow to Containers

When Docker Compose starts the system, configuration is injected into containers through two mechanisms:

Diagram: Complete Configuration Injection Flow

Sources: .env.sample1 mosquitto.conf:1-6


Security Best Practices

Secret Management Checklist

Before proceeding to Deployment, verify the following security measures:

CheckVerification CommandExpected Result
.env file existsls -la .envFile present
.env contains tokencat .envCLOUDFLARE_TUNNEL_TOKEN=eyJ...
.env is git-ignoredgit status.env not listed
.gitignore existscat .gitignoreContains .env on line 1
mosquitto.conf existsls -la mosquitto.confFile present

Common Configuration Mistakes

Mistake 1: Committing.env to Git

The .gitignore1 file prevents this, but if you modify .gitignore, ensure .env remains excluded:

Mistake 2: Missing Token in.env

If .env.sample1 placeholder remains:

CLOUDFLARE_TUNNEL_TOKEN=your_token  # WRONG - must be actual token

The cloudflared container will fail to authenticate. Verify token format (JWT-like structure starting with eyJ).

Mistake 3: Incorrect File Permissions

The .env file should be readable by the Docker daemon but protected from other users:

Sources: .gitignore:1-2 .env.sample1


Configuration Validation

Before proceeding to deployment, validate your configuration:

1. Environment Variables

2. Mosquitto Configuration Syntax

The Mosquitto configuration syntax can be pre-validated (requires mosquitto installed locally):

If mosquitto is not installed locally, syntax validation will occur during container startup in Deployment.

3. File Structure Verification

Expected repository structure after configuration:

docker-mqtt-mosquitto-cloudflare-tunnel/
├── .env                    # Created by you, contains token
├── .env.sample             # Repository template
├── .gitignore              # Excludes .env
├── docker-compose.yml      # Orchestration configuration
├── mosquitto.conf          # MQTT broker configuration
└── README.md

Sources: .env.sample1 mosquitto.conf:1-6 .gitignore:1-2


Next Steps

With local configuration complete:

  1. Environment Variables : .env file created with valid CLOUDFLARE_TUNNEL_TOKEN
  2. Mosquitto Configuration : mosquitto.conf defines listeners on ports 1883 and 9001
  3. Security : .env excluded from version control via .gitignore1

Proceed to Deployment to start the containerized services using Docker Compose.

For detailed configuration reference, see Configuration Reference.

For understanding how these configurations map to Docker containers, see Docker Compose Orchestration.

Sources: .env.sample1 mosquitto.conf:1-6 .gitignore:1-2

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Deployment

Loading…

Deployment

Relevant source files

This page covers the process of deploying the Docker MQTT Mosquitto with Cloudflare Tunnel system using Docker Compose. It assumes you have completed the Cloudflare Tunnel setup (see Cloudflare Tunnel Setup) and local configuration (see Local Configuration). For production deployment considerations, see Production Deployment Considerations. For detailed troubleshooting, see Troubleshooting.

Starting the System

With the .env file configured and mosquitto.conf in place, deploy the system using Docker Compose:

This command starts both the mosquitto and cloudflared containers as defined in docker-compose.yml:3-17 The command runs in the foreground, displaying logs from both containers in real-time.

Deployment Modes

CommandBehaviorUse Case
docker compose upForeground mode with logsDevelopment, debugging, initial testing
docker compose up -dDetached mode (background)Production, long-running deployments
docker compose up --buildRebuild images before startingAfter configuration changes requiring rebuild

Sources: README.md:74-78 docker-compose.yml:1-18


Deployment Sequence

The following sequence diagram shows the complete deployment lifecycle from command execution to operational state:

Deployment Initialization Flow

sequenceDiagram
    participant User
    participant DockerCompose as "docker-compose\n(Orchestrator)"
    participant DockerEngine as "Docker Engine"
    participant MosqContainer as "mosquitto\n(Container)"
    participant CFDContainer as "cloudflared\n(Container)"
    participant CFTunnel as "Cloudflare Tunnel\n(Service)"
    participant MosqProcess as "Mosquitto Process\n(MQTT Broker)"
    
    User->>DockerCompose: docker compose up
    DockerCompose->>DockerEngine: Parse docker-compose.yml
    DockerEngine->>DockerEngine: Create bridge network
    
    Note over DockerEngine,MosqContainer: Start mosquitto service
    DockerEngine->>MosqContainer: Pull eclipse-mosquitto:latest
    DockerEngine->>MosqContainer: Create container
    DockerEngine->>MosqContainer: Mount ./mosquitto.conf to /mosquitto/config/
    MosqContainer->>MosqProcess: Start mosquitto daemon
    MosqProcess->>MosqProcess: Read /mosquitto/config/mosquitto.conf
    MosqProcess->>MosqProcess: Bind listener on port 1883
    MosqProcess->>MosqProcess: Bind listener on port 9001 (websockets)
    MosqProcess-->>DockerEngine: Container running
    
    Note over DockerEngine,CFDContainer: Start cloudflared service
    DockerEngine->>CFDContainer: Pull cloudflare/cloudflared:latest
    DockerEngine->>CFDContainer: Create container
    DockerEngine->>CFDContainer: Inject CLOUDFLARE_TUNNEL_TOKEN from .env
    CFDContainer->>CFDContainer: Execute: tunnel --no-autoupdate run --token
    CFDContainer->>CFTunnel: Establish tunnel connection
    CFTunnel-->>CFDContainer: Tunnel established
    CFDContainer->>CFDContainer: Resolve mosquitto via DNS
    CFDContainer-->>DockerEngine: Container running
    
    DockerEngine-->>DockerCompose: Both services started
    DockerCompose-->>User: Display container logs
    
    Note over User,MosqProcess: System operational

Sources: docker-compose.yml:1-18 README.md:74-78


Container Startup Behavior

Each service defined in docker-compose.yml:3-17 has specific startup characteristics:

mosquitto Container

The mosquitto container starts with the following configuration:

Upon startup, the Mosquitto process reads the mounted configuration file and initializes two listeners as specified in mosquitto.conf.

cloudflared Container

The cloudflared container starts with the following configuration:

The --no-autoupdate flag prevents the container from attempting to update itself, maintaining version consistency with the Docker image.

Sources: docker-compose.yml:3-18


Verifying Deployment

After starting the system, verify that both containers are running and the tunnel is operational.

Container Status Verification Flow

graph TD
    Start["Deployment Started"]
CheckContainers["docker compose ps"]
VerifyStatus{"Both containers\nSTATUS=Up?"}
CheckLogs["docker compose logs"]
VerifyMosquitto{"Mosquitto logs show\nlisteners bound?"}
VerifyCloudflared{"Cloudflared logs show\ntunnel registered?"}
TestConnection["Test MQTT connection\nto public hostname"]
Success["Deployment Verified"]
Failure["Deployment Failed"]
Start --> CheckContainers
 
   CheckContainers --> VerifyStatus
 
   VerifyStatus -->|No| Failure
 
   VerifyStatus -->|Yes| CheckLogs
 
   CheckLogs --> VerifyMosquitto
 
   VerifyMosquitto -->|No| Failure
 
   VerifyMosquitto -->|Yes| VerifyCloudflared
 
   VerifyCloudflared -->|No| Failure
 
   VerifyCloudflared -->|Yes| TestConnection
 
   TestConnection -->|Connection successful| Success
 
   TestConnection -->|Connection failed| Failure

Sources: README.md:82-84


Check Container Status

List running containers and their status:

Expected output should show both containers with STATUS as Up:

NAMEIMAGESTATUSPORTS
mosquittoeclipse-mosquitto:latestUp X minutes1883/tcp, 9001/tcp
cloudflaredcloudflare/cloudflared:latestUp X minutes

Sources: docker-compose.yml:4-17


View Container Logs

View logs from all services:

View logs from a specific service:

Follow logs in real-time:

Expected Mosquitto Logs

Successful Mosquitto startup logs include:

mosquitto    | 1234567890: mosquitto version X.X.X starting
mosquitto    | 1234567890: Config loaded from /mosquitto/config/mosquitto.conf
mosquitto    | 1234567890: Opening ipv4 listen socket on port 1883.
mosquitto    | 1234567890: Opening websockets listen socket on port 9001.
mosquitto    | 1234567890: mosquitto version X.X.X running

Expected Cloudflared Logs

Successful tunnel connection logs include:

cloudflared  | Registered tunnel connection
cloudflared  | Connection <UUID> registered connIndex=0
cloudflared  | Route propagating

Sources: docker-compose.yml:1-18


Verify Public Accessibility

After deployment, the MQTT broker is accessible at https://<subdomain>.<domain>:443, where the subdomain and domain are configured in the Cloudflare Tunnel public hostname settings (see Cloudflare Tunnel Setup).

Important: The broker is not directly accessible on ports 1883 or 9001 from the public internet. All traffic is routed through Cloudflare’s network on port 443 using HTTPS/WSS protocols. Internal container communication uses HTTP on port 9001.

Sources: README.md:82-84


Container Lifecycle Management

Container State Transitions

Sources: docker-compose.yml:9-15


Stopping the System

Stop all containers while preserving their state:

This sends a SIGTERM signal to each container, allowing graceful shutdown. Containers can be restarted with docker compose start.

Sources: docker-compose.yml:1-18


Removing the Deployment

Stop and remove all containers, networks, and anonymous volumes:

This completely tears down the deployment. The next docker compose up will create fresh containers.

To also remove named volumes (use with caution):

Sources: docker-compose.yml:1-18


Restarting Services

Restart all services:

Restart a specific service:

Sources: docker-compose.yml:4-17


Viewing Live Container Processes

Inspect running processes inside containers:

This displays process information for each running container.


Executing Commands in Containers

Execute a command in a running container:

This opens an interactive shell inside the specified container. Useful for debugging and inspecting the container’s filesystem.

Sources: docker-compose.yml:6-13


Deployment Verification Checklist

Use this checklist to confirm successful deployment:

  • .env file exists with valid CLOUDFLARE_TUNNEL_TOKEN
  • mosquitto.conf file exists in the project root
  • docker compose ps shows both containers with STATUS: Up
  • docker compose logs mosquitto shows listeners bound on ports 1883 and 9001
  • docker compose logs cloudflared shows tunnel registered and connection established
  • Public hostname resolves to Cloudflare’s network
  • MQTT client can connect to https://<subdomain>.<domain>:443

Sources: README.md:74-84 docker-compose.yml:1-18


Common Deployment Issues

This section provides brief guidance on common deployment failures. For comprehensive troubleshooting, see Troubleshooting.

IssuePossible CauseQuick Resolution
Container exits immediatelyMissing or invalid CLOUDFLARE_TUNNEL_TOKENVerify .env file and token validity
cloudflared fails to startToken expired or revokedRegenerate token in Cloudflare dashboard
mosquitto fails to startSyntax error in mosquitto.confCheck configuration file syntax
Cannot connect to public URLTunnel not registered or hostname misconfiguredVerify public hostname configuration in Cloudflare
Port binding errorsPort already in use on hostContainers do not expose ports to host by default; check for conflicting services

Sources: README.md84 docker-compose.yml:1-18


Restart Policy Behavior

Both containers are configured with restart: unless-stopped (docker-compose.yml:9-15), which provides automatic recovery:

  • Container Crash: Automatically restarts
  • Docker Daemon Restart: Automatically restarts (unless manually stopped)
  • Manual Stop: Does not automatically restart until explicitly started

This policy ensures high availability without manual intervention after transient failures or system reboots.

Sources: docker-compose.yml:9-15


Next Steps

After successful deployment:

  1. Test MQTT connectivity using an MQTT client (see Testing Connection)
  2. Review component documentation (see Components)
  3. Implement monitoring (see Monitoring and Health Checks)
  4. Consider production hardening (see Production Deployment Considerations)

Sources: README.md:82-84

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Components

Loading…

Components

Relevant source files

This document provides detailed technical documentation of the major components that constitute the Docker MQTT Mosquitto Cloudflare Tunnel system. Each component is examined in terms of its Docker service configuration, runtime behavior, and integration with other system components.

For architectural overview and how these components interact at a system level, see System Architecture. For deployment instructions, see Deployment. For advanced branch-specific features like ACLs and encryption, see Advanced Topics.

Component Overview

The system consists of three primary components orchestrated by Docker Compose:

ComponentContainer NameImagePrimary Function
Mosquitto MQTT Brokermosquittoeclipse-mosquitto:latestMQTT message broker providing pub/sub messaging
Cloudflared Tunnel Clientcloudflaredcloudflare/cloudflare:latestSecure tunnel client connecting broker to Cloudflare network
Docker ComposeN/AN/AService orchestration and configuration management

Component Interaction Diagram

Sources : docker-compose.yml, README.md, mosquitto.conf

Mosquitto MQTT Broker

The Mosquitto service provides MQTT protocol brokering functionality. This component runs the Eclipse Mosquitto broker inside a Docker container, configured for WebSocket and TCP connections with anonymous access enabled.

Service Definition

The Mosquitto service is defined in docker-compose.yml:4-9 with the following configuration:

PropertyValuePurpose
imageeclipse-mosquitto:latestOfficial Mosquitto Docker image
container_namemosquittoFixed container name for internal DNS resolution
volumes./mosquitto.conf:/mosquitto/config/mosquitto.confMounts configuration file into container
restartunless-stoppedAutomatic restart policy for service reliability

The container name mosquitto is critical as it serves as the internal DNS hostname that cloudflared uses to proxy traffic (referenced in Cloudflare tunnel configuration as mosquitto:9001).

Sources : docker-compose.yml:4-9, README.md:62

graph LR
    subgraph MosquittoContainer["mosquitto Container"]
subgraph ListenerConfig["mosquitto.conf Configuration"]
Listener1["listener 1883\nprotocol mqtt"]
Listener2["listener 9001\nprotocol websockets"]
end
        
        subgraph BrokerCore["Mosquitto Broker Process"]
MQTTEngine["MQTT Protocol Engine"]
end
        
 
       Listener1 -->|standard TCP/IP| MQTTEngine
 
       Listener2 -->|WebSocket framing| MQTTEngine
    end
    
    subgraph ExternalAccess["External Access Paths"]
TCPClient["TCP MQTT Clients\n(port 1883)"]
WSClient["WebSocket Clients\n(via cloudflared proxy)"]
end
    
 
   TCPClient -.->|internal network only not exposed via tunnel| Listener1
 
   WSClient -->|proxied through cloudflared:mosquitto:9001| Listener2

MQTT Listeners

The Mosquitto broker is configured with two distinct listeners, each serving different client connection patterns:

Listener Configuration Overview

Sources : mosquitto.conf, README.md:62, docker-compose.yml:4-9

Standard TCP Listener (Port 1883)

Configured in mosquitto.conf as:

listener 1883
protocol mqtt

This listener provides standard MQTT-over-TCP connectivity on the default MQTT port. It is accessible within the Docker internal network but is not exposed through the Cloudflare tunnel in the default configuration.

Use cases :

  • Direct connections from other Docker containers on the same network
  • Internal service-to-service MQTT communication
  • Local development and testing

WebSocket Listener (Port 9001)

Configured in mosquitto.conf as:

listener 9001
protocol websockets

This listener provides MQTT-over-WebSocket connectivity, which is the primary listener exposed through the Cloudflare tunnel. The cloudflared service proxies external traffic to mosquitto:9001 as configured in the Cloudflare Zero Trust dashboard.

Use cases :

  • Browser-based MQTT clients
  • Web applications requiring MQTT connectivity
  • Clients behind restrictive firewalls that block non-HTTP protocols
  • The primary access method for external clients via Cloudflare tunnel

Sources : mosquitto.conf, README.md:55-66

Anonymous Access

The Mosquitto broker is configured with anonymous access enabled, meaning no authentication is required to connect or publish/subscribe to topics.

Configuration :

allow_anonymous true

This setting in mosquitto.conf permits any client that can reach the broker to perform MQTT operations without credentials.

Security implications :

AspectImplication
AuthenticationNone - all clients are accepted
AuthorizationNo topic-level access control in main branch
Trust modelBroker trusts all traffic from cloudflared
Network securitySecurity relies entirely on Cloudflare tunnel and network isolation

The security model assumes that network-level access control (via Cloudflare tunnel) provides sufficient protection. For environments requiring topic-level access control, see Topic Access Control (ACL) which documents the protected-no-wildcard branch implementation.

Sources : mosquitto.conf, README.md:5-11

Cloudflared Tunnel Service

The cloudflared service establishes a secure, outbound-only connection to Cloudflare’s network, enabling external MQTT clients to reach the internal Mosquitto broker without exposing inbound ports on the host.

Service Definition

The cloudflared service is defined in docker-compose.yml:11-17:

PropertyValuePurpose
imagecloudflare/cloudflared:latestOfficial Cloudflare tunnel client image
container_namecloudflaredFixed container name
commandtunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}Starts tunnel with authentication token
restartunless-stoppedAutomatic restart policy
environmentCLOUDFLARE_TUNNEL_TOKENPasses tunnel authentication token from .env file

Sources : docker-compose.yml:11-17

Tunnel Authentication and Establishment

Tunnel Connection Flow

Sources : docker-compose.yml:11-17, .env.sample, README.md:47-53

Command Line Arguments

The command directive in docker-compose.yml14 specifies:

tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}
ArgumentPurpose
tunnelPrimary cloudflared operation mode
--no-autoupdateDisables automatic updates (container image updates handled via Docker)
runRuns the tunnel in persistent mode
--tokenAuthenticates with Cloudflare using provided token
${CLOUDFLARE_TUNNEL_TOKEN}Environment variable interpolation from .env file

The token is obtained during tunnel creation in the Cloudflare Zero Trust dashboard (see Cloudflare Tunnel Configuration) and stored in the .env file as documented in .env.sample

Sources : docker-compose.yml:14, .env.sample, README.md:47-53

Traffic Proxying

Once the tunnel is established, cloudflared proxies incoming traffic from Cloudflare’s edge to the internal Mosquitto broker. The routing configuration is managed in the Cloudflare Zero Trust dashboard, not in local configuration files.

The public hostname configuration in Cloudflare maps to the service URL mosquitto:9001, where:

  • mosquitto resolves via Docker’s internal DNS to the container with container_name: mosquitto
  • Port 9001 corresponds to the WebSocket listener configured in mosquitto.conf

Sources : README.md:55-66, docker-compose.yml

Docker Compose Orchestration

Docker Compose provides service orchestration, dependency management, and configuration binding for the system.

Service Orchestration Model

Docker Compose Component Relationships

Sources : docker-compose.yml

Network Configuration

Docker Compose creates a default bridge network for service communication. The network configuration is implicit (no explicit networks section in docker-compose.yml).

Network characteristics :

AspectBehavior
Network typeBridge network (default)
Network name{project_name}_default (typically docker-mqtt-mosquitto-cloudflare-tunnel_default)
DNS resolutionContainer names resolve to container IPs (mosquitto → container IP)
Inter-service communicationAll services can reach each other using container names
External connectivityServices can initiate outbound connections (used by cloudflared)

The internal DNS resolution is critical: when the Cloudflare tunnel configuration specifies mosquitto:9001, Docker’s embedded DNS server resolves mosquitto to the IP address of the container with container_name: mosquitto.

Sources : docker-compose.yml, README.md:62

Volume Management

The Mosquitto service uses a bind mount to inject configuration:

Volume mapping details :

Source (Host)Target (Container)Mount TypePurpose
./mosquitto.conf/mosquitto/config/mosquitto.confBind mountProvides broker configuration at runtime

The configuration file is read by the Mosquitto broker on startup. Changes to mosquitto.conf require container restart to take effect:

No data persistence volumes are configured in the default setup. For persistent message storage, see Advanced Topics and the protected-no-wildcard branch documentation.

Sources : docker-compose.yml:7-8, mosquitto.conf

Environment Variable Injection

The cloudflared service receives environment variables via docker-compose.yml:16-17:

This syntax instructs Docker Compose to:

  1. Read CLOUDFLARE_TUNNEL_TOKEN from the .env file in the same directory
  2. Pass it as an environment variable to the cloudflared container
  3. Make it available for substitution in the command directive via ${CLOUDFLARE_TUNNEL_TOKEN}

The .env file is not version-controlled (excluded via .gitignore). Developers create it from .env.sample as documented in Environment Variables.

Sources : docker-compose.yml:16-17, .env.sample

Restart Policies

Both services use restart: unless-stopped:

Restart behavior :

ScenarioBehavior
Container exits with errorAutomatically restarts
Container exits cleanly (exit 0)Automatically restarts
Docker daemon restartsContainers restart automatically
Manual docker stopContainer remains stopped after Docker daemon restart
System rebootContainers restart if Docker daemon starts automatically

This policy ensures service availability while allowing manual control via docker compose down or docker stop.

Sources : docker-compose.yml:9,15

Service Lifecycle Management

Common Docker Compose Commands :

CommandEffect
docker compose upCreates and starts both services in foreground
docker compose up -dCreates and starts both services in background (detached)
docker compose downStops and removes containers, networks
docker compose restartRestarts all services
docker compose restart mosquittoRestarts only Mosquitto service
docker compose logsShows logs from all services
docker compose logs -f cloudflaredFollows logs from cloudflared service

For detailed deployment procedures, see Deployment.

Sources : docker-compose.yml, README.md:68-72

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

MQTT Protocol Listeners

Loading…

MQTT Protocol Listeners

Relevant source files

Purpose and Scope

This document describes the MQTT listener configuration in the Mosquitto broker, specifically the two listeners defined in mosquitto.conf:1-6 It explains the purpose, protocol, and routing behavior of each listener. For broader Mosquitto broker configuration, see Mosquitto Configuration. For access control settings, see Anonymous Access. For Cloudflare tunnel routing configuration, see Cloudflared Tunnel Service.

Sources : mosquitto.conf, README.md


Listener Configuration Overview

The Mosquitto broker is configured with two distinct listeners, each serving different transport protocols and use cases. Both listeners are defined in mosquitto.conf:1-6 and operate simultaneously within the mosquitto container.

<old_str>

TCP Listener"] end 
    
    
    subgraph external["External Network"]
        Internet["Public Internet"]
    end
    
    OtherContainer -->|"Direct MQTT/TCP"| MosquittoPort1883
    Internet -.->|"Not Accessible"| MosquittoPort1883
    
    
    
    **Listener Configuration Structure**
    
    | Listener | Port | Protocol | Anonymous Access | Primary Use Case |
    |----------|------|----------|------------------|------------------|
    | <FileRef file-url="https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L1-L1" min=1  file-path="mosquitto.conf">Hii</FileRef> | 1883 | Standard MQTT/TCP | Enabled globally <FileRef file-url="https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L2-L2" min=2  file-path="mosquitto.conf">Hii</FileRef> | Direct MQTT client connections |
    | <FileRef file-url="https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L4-L5" min=4 max=5 file-path="mosquitto.conf">Hii</FileRef> | 9001 | MQTT over WebSockets | Enabled globally <FileRef file-url="https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L2-L2" min=2  file-path="mosquitto.conf">Hii</FileRef> | Browser-based clients, Cloudflare tunnel |
    
    **Sources**: mosquitto.conf
    
    ---
    
    ## Listener 1883: Standard MQTT TCP
    
    The first listener, configured at <FileRef file-url="https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L1-L1" min=1  file-path="mosquitto.conf">Hii</FileRef> binds to port 1883 and provides standard MQTT protocol over TCP. This is the default MQTT port and is used by most native MQTT client libraries.
    
    ### Configuration Directives
    
    

listener 1883 allow_anonymous true
    
    
    **Directive Breakdown**:
    - `listener 1883` <FileRef file-url="https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L1-L1" min=1  file-path="mosquitto.conf">Hii</FileRef>: Binds the Mosquitto broker to TCP port 1883
    - `allow_anonymous true` <FileRef file-url="https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L2-L2" min=2  file-path="mosquitto.conf">Hii</FileRef>: Applies globally to all listeners, allowing clients to connect without authentication
    
    ### Protocol Characteristics
    
    This listener implements the standard MQTT 3.1.1 and MQTT 5.0 protocols over raw TCP connections. It supports:
    - Binary MQTT protocol framing
    - Persistent TCP connections
    - Quality of Service (QoS) levels 0, 1, and 2
    - Last Will and Testament (LWT) messages
    - Retained messages
    
    ### Network Accessibility
    
    The 1883 listener is **not** directly exposed through the Cloudflare tunnel in the current configuration. It operates only within Docker's internal network, accessible to other containers in the same Docker Compose stack.
    
    ```mermaid
    graph LR
        subgraph "Docker Internal Network"
            OtherContainer["Other Docker Container"]
            MosquittoPort1883["mosquitto:1883<br/>TCP Listener"]
        end
        
        subgraph "External Network"
            Internet["Public Internet"]
        end
        
        OtherContainer -->|"Direct MQTT/TCP"| MosquittoPort1883
        Internet -.->|"Not Accessible"| MosquittoPort1883
    

**Sources** : mosquitto.conf, README.md

* * *

## Listener 9001: MQTT over WebSockets

The second listener, configured at [mosquitto.conf:4-5](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L4-L5) binds to port 9001 and provides MQTT protocol encapsulated over WebSockets. This is the primary listener exposed to external clients via the Cloudflare tunnel.

### Configuration Directives
    
    
    listener 9001
    protocol websockets
    

**Directive Breakdown** :

  * `listener 9001` [mosquitto.conf4](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L4-L4): Binds the Mosquitto broker to TCP port 9001
  * `protocol websockets` [mosquitto.conf5](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L5-L5): Configures this listener to accept WebSocket connections with MQTT payloads



### Protocol Characteristics

This listener encapsulates MQTT protocol messages within WebSocket frames, enabling:

  * HTTP/WebSocket upgrade handshake
  * Browser-based MQTT clients (JavaScript)
  * Compatibility with web proxies and CDNs
  * TLS/SSL termination at the proxy layer (Cloudflare)



The WebSocket protocol wrapper does not alter the MQTT protocol itself; it only provides an HTTP-compatible transport layer.

### Cloudflare Tunnel Routing

The 9001 listener is configured as the target for the Cloudflare tunnel in the Zero Trust dashboard. The tunnel configuration specifies `mosquitto:9001` as the service URL [README.md62](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/README.md#L62-L62) routing external MQTT WebSocket connections through the `cloudflared` container to this listener.


### Network Accessibility

Unlike the 1883 listener, the 9001 listener is indirectly exposed to the public internet through the Cloudflare tunnel. The routing path is:

  1. External client connects to Cloudflare public hostname
  2. Cloudflare Edge routes to `cloudflared` container via tunnel
  3. `cloudflared` proxies HTTP traffic to `mosquitto:9001`
  4. Mosquitto 9001 listener accepts WebSocket connection



**Sources** : mosquitto.conf, README.md

* * *

## Traffic Routing Architecture

The following diagram illustrates how traffic is routed to each listener based on the source and transport protocol:


**Routing Configuration References** :

  * Cloudflare tunnel target: `mosquitto:9001` [README.md62](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/README.md#L62-L62)
  * WebSocket listener: [mosquitto.conf:4-5](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L4-L5)
  * TCP listener: [mosquitto.conf1](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L1-L1)
  * Container name resolution: `mosquitto` from `docker-compose.yml`



**Sources** : mosquitto.conf, README.md

* * *

## Configuration Reference

### Listener Directive Syntax

The `listener` directive defines a network interface and port for the broker to bind to:
    
    
    listener <port> [bind_address]
    

**Current Configuration** :

  * [mosquitto.conf1](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L1-L1): `listener 1883` \- Binds to port 1883 on all interfaces (no bind_address specified)
  * [mosquitto.conf4](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L4-L4): `listener 9001` \- Binds to port 9001 on all interfaces



### Protocol Directive

The `protocol` directive specifies the transport protocol for a listener:
    
    
    protocol <mqtt|websockets>
    

**Current Configuration** :

  * Listener 1883: No protocol directive (defaults to `mqtt`)
  * [mosquitto.conf5](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L5-L5): `protocol websockets` \- Configures listener 9001 for WebSocket transport



### Anonymous Access Directive

The `allow_anonymous` directive controls whether clients can connect without authentication:
    
    
    allow_anonymous <true|false>
    

**Current Configuration** :

  * [mosquitto.conf2](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L2-L2): `allow_anonymous true` \- Applies globally to all listeners



This directive appears once and affects both listeners. For production deployments requiring authentication, this should be set to `false` and appropriate authentication mechanisms configured. See [Anonymous Access](2-1-2-authentication-and-access-control.md) for detailed security implications.

### Configuration Interaction Matrix

Configuration Aspect| Listener 1883| Listener 9001  
---|---|---  
**Port**|  1883 [mosquitto.conf1](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L1-L1)| 9001 [mosquitto.conf4](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L4-L4)  
**Protocol**|  MQTT/TCP (implicit default)| WebSockets [mosquitto.conf5](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L5-L5)  
**Anonymous Access**|  Enabled [mosquitto.conf2](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L2-L2)| Enabled [mosquitto.conf2](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/mosquitto.conf#L2-L2)  
**Cloudflare Routing**|  Not configured| Configured [README.md62](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/README.md#L62-L62)  
**External Accessibility**|  Docker internal only| Via Cloudflare tunnel  
  
**Sources** : mosquitto.conf, README.md

* * *

## Use Case Selection

### When to Use Listener 1883 (TCP)

  * **Native MQTT client libraries** : Python Paho, Mosquitto C library, MQTT.js with TCP
  * **High-performance IoT devices** : Minimal protocol overhead
  * **Internal Docker services** : Direct container-to-container communication
  * **Legacy MQTT applications** : Standard MQTT port compatibility



### When to Use Listener 9001 (WebSockets)

  * **Browser-based clients** : JavaScript MQTT clients in web applications
  * **Cloudflare tunnel access** : External clients connecting via the public hostname
  * **Firewall-restricted networks** : HTTP/WebSocket traffic often allowed through corporate firewalls
  * **CDN/proxy compatibility** : Works with HTTP-based reverse proxies



### Current System Configuration

The current deployment configures the Cloudflare tunnel to route exclusively to listener 9001. This means:

  * External clients **must** use MQTT over WebSockets
  * The public hostname [README.md60](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/README.md#L60-L60) resolves to listener 9001
  * Listener 1883 remains available only for internal Docker network access



To expose listener 1883 externally, additional Cloudflare tunnel configuration would be required, though this is uncommon since WebSocket transport provides broader compatibility.

**Sources** : mosquitto.conf, README.md

Dismiss

Refresh this wiki

Enter email to refresh
<hr style="margin-top: 3rem; border: none; border-top: 1px solid #e0e0e0;">

<div class="doc-footer" style="text-align: center; padding: 1.5rem 0; color: #666">
  <p style="margin: 0;">Documentation generated on January 04, 2026 at 00:40 UTC</p>
  
  <p style="margin: 0.5rem 0 0 0;">
    <a href="https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel" rel="noopener noreferrer" style="color: #667eea; text-decoration: none;">jzombie/docker-mqtt-mosquitto-cloudflare-tunnel</a>
  </p>
  
</div>
GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Authentication and Access Control

Loading…

Authentication and Access Control

Relevant source files

This document explains the authentication and access control mechanisms available for the Mosquitto MQTT broker. The current configuration uses anonymous access, allowing any client that can reach the broker to connect without credentials. This page covers the implementation details, security implications, and alternative authentication strategies.

The Mosquitto broker supports multiple authentication methods:

  • Anonymous Access : No credentials required (current configuration)
  • Username/Password Authentication : Credential-based access control with password files
  • Access Control Lists (ACLs) : Topic-level permissions based on usernames
  • Plugin-Based Authentication : Custom authentication mechanisms via plugins

For the complete system security architecture, see Security Model. For advanced authentication configurations, see Protected Branch Features.


Current Configuration: Anonymous Access

The Mosquitto broker in this repository is configured for anonymous access, meaning no authentication is required for client connections. This is controlled by a single directive in the configuration file.

Configuration Location

The anonymous access setting is defined in mosquitto.conf2:

allow_anonymous true

This directive applies globally to all listeners defined in the configuration file. In this system, anonymous access affects both the standard MQTT listener on port 1883 and the WebSocket listener on port 9001 (see MQTT Protocol Listeners for details on these listeners).

Whatallow_anonymous true Means

ConfigurationBehavior
allow_anonymous trueClients can connect without username/password
allow_anonymous falseClients must provide valid credentials
(directive omitted)Defaults to false in Mosquitto 2.0+

Sources: mosquitto.conf


Anonymous Access Connection Flow

Connection Flow Diagram

When allow_anonymous true is set in mosquitto.conf2 the Mosquitto broker accepts MQTT CONNECT packets regardless of whether they contain username/password credentials. The broker does not perform any authentication checks and immediately grants access to all MQTT operations.

Authentication Decision Logic

MQTT CONNECT ContainsCredentials Checked?Connection Result
Username + PasswordNoAccepted
Username onlyNoAccepted
No credentialsNoAccepted

Sources: mosquitto.conf, README.md


Access Permissions with Anonymous Access

Unrestricted Topic Access

With allow_anonymous true and no ACL file configured in mosquitto.conf clients have unrestricted access:

Permission TypeAnonymous Client Capabilities
SUBSCRIBECan subscribe to any topic, including wildcards (#, +)
PUBLISHCan publish to any topic
RETAINCan set retained messages on any topic
QoS LevelsFull access to QoS 0, 1, and 2
Topic RestrictionsNone - all topics accessible
Will MessagesCan set last-will-and-testament messages

Authentication Check Flow

Sources: mosquitto.conf


Security Implications

Trust Boundary Analysis

The security model for this configuration relies entirely on network-level access control rather than application-level authentication:

Security LayerProtection ProvidedConfiguration
Cloudflare EdgeDDoS protection, traffic filteringCloudflare Dashboard
Cloudflare TunnelEncrypted transport, no inbound portsCLOUDFLARE_TUNNEL_TOKEN
Docker NetworkInternal isolation between containersdocker-compose.yml
Mosquitto AuthenticationNone - anonymous access enabledallow_anonymous: true

Sources: mosquitto.conf, README.md

Threat Model Considerations

What Anonymous Access Protects Against:

  • Direct internet exposure (mitigated by Cloudflare Tunnel)
  • Port scanning and direct connection attempts (no inbound firewall rules required)
  • DDoS attacks at the network level (handled by Cloudflare Edge)

What Anonymous Access Does NOT Protect Against:

  • Unauthorized message publication by any client that reaches the broker
  • Eavesdropping on topics by any connected client
  • Malicious clients subscribing to all topics using # wildcard
  • Topic flooding or message spam from authenticated clients
  • Multi-tenant isolation (all clients can access all topics)

Sources: README.md


Appropriate Use Cases

When Anonymous Access Is Suitable

Anonymous access is appropriate in the following scenarios:

ScenarioRationale
Single-User SystemsOne person controls all MQTT clients and traffic
Private/Home NetworksNetwork-level security already restricts access
Trusted Development EnvironmentsQuick setup for testing without credential management
Internal IoT NetworksAll devices within a trusted network boundary
Proof-of-Concept DeploymentsRapid prototyping without authentication complexity

When Authentication Is Required

Authentication should be implemented when:

  • Multiple users need isolated topic spaces
  • Different clients require different permission levels
  • Production deployments with untrusted client access
  • Compliance or audit requirements mandate access control
  • Topic-level security policies need enforcement
  • Multi-tenant systems require user isolation

For these scenarios, see the alternative implementation in the [protected-no-wildcard branch](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/protected-no-wildcard branch) which provides username/password authentication and ACL-based topic restrictions. Detailed documentation is available at Protected Branch Features.

Sources: README.md


Configuration File Structure

The complete Mosquitto configuration from mosquitto.conf:1-5:

listener 1883
allow_anonymous true

listener 9001
protocol websockets

Configuration Analysis

LineDirectiveEffectScope
1listener 1883Defines standard MQTT listenerPort 1883
2allow_anonymous trueEnables anonymous accessGlobal (all listeners)
3(blank)Line separator-
4listener 9001Defines WebSocket listenerPort 9001
5protocol websocketsSpecifies WebSocket protocolPort 9001 only

Key Observations

  • The allow_anonymous true directive applies globally to both listeners
  • No password_file directive is present
  • No acl_file directive is present
  • No allow_anonymous override on individual listeners
  • This creates a uniform anonymous access policy across MQTT and WebSocket protocols

Sources: mosquitto.conf

graph TB
    ComposeFile["docker-compose.yml"]
MosqConf["mosquitto.conf"]
MosqContainer["mosquitto Container\n(eclipse-mosquitto:latest)"]
subgraph "Configuration Flow"
 
       ComposeFile -->|Defines service| MosqContainer
 
       MosqConf -->|Volume mount ./mosquitto.conf:/mosquitto/config/mosquitto.conf| MosqContainer
    end
    
    subgraph "Anonymous Access Config"
        Line2["Line 2: allow_anonymous true"]
NoACL["No aclfile directive"]
NoPwdFile["No password_file directive"]
end
    
 
   MosqConf --> Line2
 
   MosqConf --> NoACL
 
   MosqConf --> NoPwdFile
    
    subgraph "Runtime Behavior"
        AllowAll["All connections accepted"]
NoCredCheck["No credential validation"]
FullTopicAccess["Full topic access for all clients"]
end
    
 
   Line2 --> AllowAll
 
   NoACL --> FullTopicAccess
 
   NoPwdFile --> NoCredCheck

Relationship to System Components

Container Configuration

The mosquitto service in docker-compose.yml mounts the configuration file as a volume:

This volume mount makes the allow_anonymous true setting effective when the container starts. The Mosquitto process reads this configuration file on startup and applies the anonymous access policy to all listeners.

Sources: mosquitto.conf, docker-compose.yml (referenced in diagrams)


Alternative Authentication Configurations

Username/Password Authentication

To enable credential-based authentication, modify mosquitto.conf to include:

allow_anonymous false
password_file /mosquitto/config/passwordfile

Create the password file using the mosquitto_passwd utility:

Authentication Flow with Credentials

ConfigurationClient Connect BehaviorResult
allow_anonymous false
password_file setCONNECT without credentialsConnection rejected (CONNACK with return code 5)
allow_anonymous false
password_file setCONNECT with valid credentialsConnection accepted
allow_anonymous false
password_file setCONNECT with invalid credentialsConnection rejected (CONNACK with return code 4)

Access Control Lists (ACLs)

ACLs provide topic-level permission control based on usernames. Add to mosquitto.conf:

acl_file /mosquitto/config/aclfile

ACL File Example

# User 'alice' can read/write to alice/* topics
user alice
topic readwrite alice/#

# User 'bob' can only read bob/* topics  
user bob
topic read bob/#

Protected Branch Example

The [protected-no-wildcard branch](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/protected-no-wildcard branch) demonstrates a complete authentication implementation:

FeatureMain BranchProtected Branch
AuthenticationAnonymousUsername/password required
Topic AccessUnrestrictedACL-based restrictions
Wildcard SubscriptionsAllowedRestricted to user’s namespace
Configuration Filesmosquitto.conf onlymosquitto.conf + ACL file + password file
Retained MessagesUnencryptedEncrypted with gocryptfs

For implementation details, see Protected Branch Features.

Sources: README.md


Testing Anonymous Access

The CI/CD pipeline validates that the Mosquitto broker starts successfully with anonymous access enabled. The health check in .github/workflows/ci.yml verifies the container reaches a running state but does not test authentication behavior since none is required.

Testing Approach

To manually verify anonymous access behavior:

  1. Start the services: docker compose up
  2. Connect any MQTT client to the public hostname configured in Cloudflare
  3. Attempt to connect without providing credentials
  4. Verify successful connection and message publication

No username or password should be required for successful connection and full MQTT operation.

Sources: .github/workflows/ci.yml (referenced), README.md


Migration Path to Authentication

If anonymous access proves insufficient for security requirements, the system can be migrated to authenticated access by:

  1. Creating a password file with mosquitto_passwd command
  2. Adding password_file /path/to/password_file directive to mosquitto.conf
  3. Removing or changing allow_anonymous true to allow_anonymous false
  4. Optionally adding an ACL file for topic-level restrictions
  5. Updating client code to provide credentials in CONNECT packets

The [protected-no-wildcard branch](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/protected-no-wildcard branch) provides a complete reference implementation. View the [configuration differences](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/configuration differences) to understand required changes.

For production deployments requiring authentication, see Production Considerations and Topic Access Control (ACL).

Sources: README.md

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Mosquitto MQTT Broker

Loading…

Mosquitto MQTT Broker

Relevant source files

Purpose and Scope

This document provides in-depth technical documentation of the mosquitto service within the Docker MQTT Mosquitto Cloudflare Tunnel system. It covers the Docker service configuration, container image specification, volume mount strategy, network integration, and runtime characteristics of the MQTT broker component.

For detailed information about the specific MQTT listener configurations (ports 1883 and 9001), see MQTT Listeners. For security implications and configuration of anonymous client access, see Anonymous Access. For the cloudflared service that proxies external traffic to this broker, see Cloudflared Tunnel Service.

Sources: README.md15 docker-compose.yml:4-9


Overview

The mosquitto service is the core MQTT message broker in this system, responsible for accepting MQTT client connections, routing published messages to subscribers, and maintaining message state. It runs as a containerized instance of Eclipse Mosquitto, an open-source implementation of the MQTT protocol versions 5.0, 3.1.1, and 3.1.

Within the system architecture, the broker receives traffic exclusively through the cloudflared container, which proxies external MQTT connections from Cloudflare’s tunnel to the broker’s WebSocket listener on port 9001. The broker is never directly exposed to the internet, relying on Cloudflare Tunnel for secure external access.

Sources: README.md15 docker-compose.yml:4-9 Diagram 1 from system overview


Docker Service Configuration

Service Definition

The mosquitto service is defined in docker-compose.yml:4-9 as follows:

PropertyValuePurpose
imageeclipse-mosquitto:latestContainer image specification
container_namemosquittoFixed container name for DNS resolution
volumes./mosquitto.conf:/mosquitto/config/mosquitto.confConfiguration file mount
restartunless-stoppedAutomatic restart policy

The service definition is minimal by design, relying on external configuration through the volume-mounted mosquitto.conf file rather than environment variables or command-line arguments.

Sources: docker-compose.yml:4-9

Container Image Strategy

The service uses the eclipse-mosquitto:latest image tag from Docker Hub’s official Eclipse Mosquitto repository. This tag automatically pulls the most recent stable version of Mosquitto when the container is created or updated. The latest tag strategy means:

  • New deployments receive the current stable version
  • Existing deployments remain on their pulled version until explicitly updated
  • No version pinning provides automatic access to bug fixes and features
  • Manual intervention required to pull updated images (docker compose pull)

For production deployments where version stability is critical, consider pinning to a specific version tag (e.g., eclipse-mosquitto:2.0.18) instead of latest.

Sources: docker-compose.yml5


Configuration File Management

Volume Mount Architecture

Diagram: Configuration File Volume Mount Flow

The configuration file is mounted from the host filesystem into the container at /mosquitto/config/mosquitto.conf. The Mosquitto process reads this file during container startup to determine listener configurations, authentication settings, and protocol options.

Sources: docker-compose.yml:7-8 mosquitto.conf:1-6

Configuration File Contents

The mosquitto.conf:1-6 file defines the broker’s operational parameters:

listener 1883
allow_anonymous true

listener 9001
protocol websockets

This configuration establishes two distinct listeners with different protocols and purposes. The configuration is intentionally minimal, containing only the essential settings for listener setup and anonymous access control. The file is version-controlled and identical across all deployments, with no host-specific or secret values.

Changes to mosquitto.conf require a container restart to take effect, as Mosquitto reads the configuration only during startup.

Sources: mosquitto.conf:1-6 docker-compose.yml:7-8


Network Architecture

Docker Network Integration

Diagram: Service Network Connectivity

The mosquitto service participates in Docker Compose’s default bridge network, which provides automatic DNS resolution. The fixed container_name: mosquitto enables the cloudflared container to reference it by hostname when configuring the tunnel’s backend service (configured in Cloudflare Dashboard as mosquitto:9001).

No ports are exposed to the host machine in docker-compose.yml:4-9 meaning:

  • Port 1883 is accessible only within the Docker network
  • Port 9001 is accessible only within the Docker network
  • External access occurs exclusively through the cloudflared tunnel proxy
  • No inbound firewall rules are required on the host

Sources: docker-compose.yml:4-9 README.md62

Service-to-Service Communication

Diagram: Internal Service Communication Flow

The cloudflared container resolves the mosquitto hostname using Docker’s internal DNS, which maps the container_name to the container’s IP address on the bridge network. This DNS-based service discovery eliminates the need for hardcoded IP addresses or external service registries.

Sources: README.md62 docker-compose.yml:4-9


Runtime Characteristics

Container Lifecycle

The restart: unless-stopped policy in docker-compose.yml9 configures automatic container restart behavior:

EventContainer Behavior
Container crashAutomatically restarts
Docker daemon restartAutomatically restarts
Manual docker stopRemains stopped
Manual docker compose downRemoved (not restarted)
System rebootAutomatically restarts if Docker daemon starts

This policy ensures high availability while allowing manual intervention when needed. The broker restarts automatically after transient failures but respects explicit stop commands from administrators.

Sources: docker-compose.yml9

Data Persistence

The main branch configuration does not define any volume mounts for persistent data storage. This means:

  • Message persistence is disabled (all messages are in-memory only)
  • Retained messages are lost on container restart
  • Client session state is not persisted
  • QoS 1 and QoS 2 message queues are cleared on restart

For persistent message storage, the [protected-no-wildcard branch](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/protected-no-wildcard branch) implements encrypted retained message storage. See Encrypted Retained Messages for details on that advanced configuration.

Sources: docker-compose.yml:4-9 README.md:5-11

Process Architecture

Diagram: Container Process Architecture

The Mosquitto broker runs as PID 1 within its container, reading configuration from the mounted mosquitto.conf file and establishing both listener sockets. The process handles all MQTT client connections, message routing, and protocol state management as a single-threaded event loop.

Sources: docker-compose.yml:4-9 mosquitto.conf:1-6


Integration with System Components

Relationship to Cloudflared Service

The mosquitto service operates in coordination with the cloudflared service defined in docker-compose.yml:11-17 The cloudflared tunnel configuration (managed in the Cloudflare Dashboard) specifies mosquitto:9001 as the backend service URL, establishing the proxy relationship:

  1. Cloudflared container resolves mosquitto hostname via Docker DNS
  2. Cloudflared establishes HTTP connection to mosquitto container port 9001
  3. Cloudflared proxies WebSocket upgrade requests to mosquitto
  4. Mosquitto accepts WebSocket connection and initiates MQTT protocol

This architecture keeps the mosquitto container completely isolated from direct internet access while maintaining full MQTT functionality through the tunnel proxy.

Sources: README.md62 docker-compose.yml:11-17

CI/CD Testing Integration

The GitHub Actions workflow in .github/workflows/ci.yml tests the mosquitto service health during continuous integration:

The CI pipeline verifies that the mosquitto container reaches a running state, validating that:

  • The eclipse-mosquitto:latest image can be pulled
  • The container starts without configuration errors
  • The mosquitto process initializes successfully
  • The mounted configuration file is valid

For complete CI/CD pipeline documentation, see CI/CD Pipeline.

Sources: .github/workflows/ci.yml docker-compose.yml:4-9


Configuration Reference Summary

Configuration AspectLocationValue
Service namedocker-compose.yml:4mosquitto
Container namedocker-compose.yml:6mosquitto
Imagedocker-compose.yml:5eclipse-mosquitto:latest
Configuration file (host)docker-compose.yml:8./mosquitto.conf
Configuration file (container)docker-compose.yml:8/mosquitto/config/mosquitto.conf
Restart policydocker-compose.yml:9unless-stopped
Standard MQTT listenermosquitto.conf:1Port 1883
WebSocket listenermosquitto.conf:4Port 9001
Anonymous accessmosquitto.conf:2true
Data persistenceN/ANone (in-memory only)

Sources: docker-compose.yml:4-9 mosquitto.conf:1-6

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Cloudflared Tunnel Connector

Loading…

Cloudflared Tunnel Connector

Relevant source files

Purpose and Scope

This document details the cloudflared container component, which acts as the secure tunnel connector between the Cloudflare network and the local Mosquitto MQTT broker. It explains the container’s configuration, how it authenticates with Cloudflare’s infrastructure, how it establishes and maintains the tunnel connection, and how it routes incoming traffic to the Mosquitto service.

For broader system architecture context, see System Architecture. For Docker Compose orchestration details, see Docker Compose Orchestration. For environment variable configuration, see Environment Variables.


Container Overview

The cloudflared service is defined in docker-compose.yml:11-17 and runs the official Cloudflare Tunnel connector image. This container establishes an outbound-only persistent connection to Cloudflare’s network, eliminating the need for open inbound ports on the host system.

Container Specification

PropertyValuePurpose
Service NamecloudflaredDocker Compose service identifier
Container NamecloudflaredRuntime container name
Imagecloudflare/cloudflared:latestOfficial Cloudflare tunnel connector
Commandtunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}Tunnel execution with token authentication
Restart Policyunless-stoppedAutomatic restart except on manual stop
Environment VariablesCLOUDFLARE_TUNNEL_TOKENTunnel authentication credential

Sources: docker-compose.yml:11-17


Container Configuration Architecture

The following diagram maps the Docker Compose configuration elements to their runtime purposes:

Sources: docker-compose.yml:11-17

graph TB
    subgraph "docker-compose.yml Definition"
        SERVICE["services.cloudflared"]
IMAGE["image: cloudflare/cloudflared:latest"]
CONTAINER["container_name: cloudflared"]
COMMAND["command: tunnel --no-autoupdate run --token"]
RESTART["restart: unless-stopped"]
ENV_SECTION["environment:\n- CLOUDFLARE_TUNNEL_TOKEN"]
end
    
    subgraph "Runtime Configuration"
        TOKEN_VAR["${CLOUDFLARE_TUNNEL_TOKEN}\nfrom .env file"]
CMD_EXEC["Executed Command:\ntunnel --no-autoupdate run --token <actual_token>"]
end
    
    subgraph "Operational Behavior"
        CONTAINER_PROC["cloudflared Process"]
AUTO_RESTART["Automatic Restart\non Failure"]
NO_UPDATE["Update Mechanism\nDisabled"]
end
    
 
   SERVICE --> IMAGE
 
   SERVICE --> CONTAINER
 
   SERVICE --> COMMAND
 
   SERVICE --> RESTART
 
   SERVICE --> ENV_SECTION
    
 
   ENV_SECTION --> TOKEN_VAR
 
   COMMAND --> TOKEN_VAR
 
   TOKEN_VAR --> CMD_EXEC
    
 
   CMD_EXEC --> CONTAINER_PROC
 
   RESTART --> AUTO_RESTART
 
   COMMAND --> NO_UPDATE
    
 
   CONTAINER_PROC --> NO_UPDATE
 
   CONTAINER_PROC --> AUTO_RESTART

Tunnel Authentication

The cloudflared connector authenticates with Cloudflare’s infrastructure using a cryptographically secure tunnel token. This token is generated during the Cloudflare Tunnel creation process and links the local connector to a specific tunnel configuration on Cloudflare’s network.

sequenceDiagram
    participant User as "Administrator"
    participant CF_UI as "Cloudflare Dashboard"
    participant Env as ".env File"
    participant Compose as "docker-compose"
    participant CFD as "cloudflared Container"
    participant CF_Net as "Cloudflare Network"
    
    User->>CF_UI: Create tunnel via Zero Trust dashboard
    CF_UI->>CF_UI: Generate CLOUDFLARE_TUNNEL_TOKEN
    CF_UI-->>User: Display token in connector setup
    
    User->>Env: Create .env file with token
    Note over Env: CLOUDFLARE_TUNNEL_TOKEN=eyJh...
    
    User->>Compose: docker compose up
    Compose->>Env: Read CLOUDFLARE_TUNNEL_TOKEN
    Compose->>CFD: Start container with token in command
    Note over CFD: tunnel run --token <token>
    
    CFD->>CF_Net: Initiate tunnel connection with token
    CF_Net->>CF_Net: Validate token signature
    CF_Net->>CF_Net: Retrieve tunnel configuration
    CF_Net-->>CFD: Tunnel established
    
    Note over CFD,CF_Net: Persistent connection maintained

Token Lifecycle

Token Security Properties

  • Single-Use Binding: Each token is bound to a specific tunnel UUID in Cloudflare’s infrastructure
  • Environment Injection: Token is injected via environment variable substitution at container start
  • Never Logged: The --token flag ensures the token does not appear in process listings
  • Git Exclusion: The .env file containing the token is excluded via .gitignore

Sources: docker-compose.yml14 docker-compose.yml:16-17 .env.sample1 README.md53


Connection Establishment

The cloudflared container establishes an outbound-only connection to Cloudflare’s edge network. This inverted architecture eliminates the need for inbound firewall rules or public IP addresses.

Tunnel Command Structure

The command executed by the container is:

tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}
FlagPurpose
tunnelPrimary cloudflared operation mode
--no-autoupdateDisables automatic binary updates to ensure container image control
runExecutes the tunnel connector
--tokenProvides authentication credential inline

Sources: docker-compose.yml14

Connection Flow

Sources: docker-compose.yml14 README.md64 README.md67


Traffic Routing Mechanism

The cloudflared container acts as a local reverse proxy, receiving traffic from the Cloudflare tunnel and forwarding it to the mosquitto service using Docker’s internal DNS resolution.

graph LR
    subgraph "Cloudflare Configuration"
        CF_HOST["Public Hostname:\nmqtt.example.com"]
CF_SERVICE["Service Type: HTTP"]
CF_URL["URL: mosquitto:9001"]
end
    
    subgraph "Docker Network"
        DNS["Docker Internal DNS"]
MOSQ_NAME["Container Name:\nmosquitto"]
MOSQ_IP["Container IP:\n172.x.x.x"]
end
    
    subgraph "cloudflared Container"
        CFD_PROC["cloudflared Process"]
RESOLVER["DNS Resolver"]
HTTP_CLIENT["HTTP Client"]
end
    
 
   CF_HOST --> CF_SERVICE
 
   CF_SERVICE --> CF_URL
    
 
   CF_URL -.->|'mosquitto' hostname configured in tunnel| CFD_PROC
 
   CFD_PROC --> RESOLVER
 
   RESOLVER --> DNS
 
   DNS --> MOSQ_NAME
 
   MOSQ_NAME --> MOSQ_IP
 
   MOSQ_IP -.-> HTTP_CLIENT
    
 
   HTTP_CLIENT -->|HTTP to 172.x.x.x:9001| MOSQ_IP

Internal DNS Resolution

Routing Configuration

The routing is configured in the Cloudflare Dashboard during tunnel setup:

  • Public Hostname: User-defined subdomain (e.g., mqtt.example.com)
  • Service Type: HTTP (internal protocol between cloudflared and mosquitto)
  • Service URL: mosquitto:9001 (Docker service name and port)

The mosquitto hostname in the service URL is resolved by Docker’s internal DNS to the IP address of the container named mosquitto as defined in docker-compose.yml6

Sources: README.md:62-67 docker-compose.yml:4-6


Protocol Translation

The cloudflared container performs protocol translation between external HTTPS/WSS connections and internal HTTP/WebSocket connections.

External vs Internal Protocols

DirectionProtocolPortEncryption
Client → Cloudflare EdgeHTTPS/WSS443TLS (Cloudflare-managed)
Cloudflare Edge → cloudflaredTunnel ProtocolN/AEncrypted tunnel
cloudflared → mosquittoHTTP/WebSocket9001Unencrypted (internal network)

The external connection is encrypted end-to-end from client to Cloudflare’s edge, while the internal connection between cloudflared and mosquitto is unencrypted but isolated within Docker’s private bridge network.

Sources: README.md67 README.md82


Operational Characteristics

No Exposed Ports

The cloudflared container exposes no ports to the host system. All communication occurs via:

  1. Outbound tunnel connection to Cloudflare’s network
  2. Internal Docker network for communication with the mosquitto container

This design eliminates attack surface by ensuring no listening ports on the host machine.

Sources: docker-compose.yml:11-17

Restart Behavior

The container uses the unless-stopped restart policy defined in docker-compose.yml15 This ensures:

  • Automatic recovery: Container restarts on crashes or Docker daemon restarts
  • Persistent operation: Container continues running across reboots
  • Manual control: Container only stops when explicitly commanded via docker compose down or docker stop

Update Management

The --no-autoupdate flag in docker-compose.yml14 disables cloudflared’s built-in automatic update mechanism. This ensures:

  • Version pinning: Updates are controlled via Docker image version
  • Predictable behavior: No unexpected binary changes during operation
  • Image-based updates: Updates occur through docker compose pull and container recreation

Sources: docker-compose.yml:14-15


Connection State Diagram

Sources: docker-compose.yml:14-15


Environment Variable Configuration

The cloudflared container requires exactly one environment variable:

CLOUDFLARE_TUNNEL_TOKEN

  • Source: Defined in .env file (see .env.sample1)
  • Injection Method: Environment variable substitution in docker-compose.yml14
  • Format: Base64-encoded JSON containing tunnel credentials
  • Scope: Container-level environment variable
  • Security: Must not be committed to version control

The token is referenced twice in the configuration:

  1. In the command field via shell substitution: ${CLOUDFLARE_TUNNEL_TOKEN}
  2. In the environment section for explicit passing to the container

Sources: docker-compose.yml14 docker-compose.yml:16-17 .env.sample1


Security Model

Zero Inbound Exposure

The cloudflared container implements a security model where:

  • No listening ports: Container binds no ports to the host interface
  • Outbound-only connections: All connections are initiated from inside the network
  • Tunnel-based access: External access is only possible through the authenticated tunnel
  • No direct routes: No firewall rules or port forwarding required

Authentication Flow

Sources: docker-compose.yml14 docker-compose.yml:16-17 README.md53


Common Configuration Reference

Minimal Configuration

The cloudflared service requires minimal configuration:

Configuration Dependencies

DependencyLocationPurpose
Tunnel Token.env fileAuthentication credential
Tunnel ConfigurationCloudflare DashboardRouting and hostname mapping
Docker NetworkImplicit (default bridge)Container-to-container communication
mosquitto Servicedocker-compose.yml:4-9Target service for traffic forwarding

Sources: docker-compose.yml:11-17


Integration with Mosquitto

The cloudflared container integrates with the mosquitto container through Docker’s internal networking:

Service Discovery

  • DNS Name: mosquitto (from docker-compose.yml4)
  • Container Name: mosquitto (from docker-compose.yml6)
  • Target Port: 9001 (WebSocket listener)
  • Resolution: Automatic via Docker’s embedded DNS server
graph TB
    subgraph "Host System"
        HOST["No Exposed Ports"]
end
    
    subgraph "Docker Bridge Network"
        CFD_CONTAINER["cloudflared Container\nNo EXPOSE directive"]
MOSQ_CONTAINER["mosquitto Container\nNo EXPOSE directive"]
DOCKER_DNS["Docker DNS Server"]
end
    
    subgraph "External Network"
        CF_EDGE["Cloudflare Edge\nPublic HTTPS:443"]
end
    
 
   CF_EDGE <-->|Outbound tunnel connection| CFD_CONTAINER
 
   CFD_CONTAINER <-->|DNS query: mosquitto| DOCKER_DNS
 
   DOCKER_DNS -->|Resolves to container IP| MOSQ_CONTAINER
 
   CFD_CONTAINER -->|HTTP:9001| MOSQ_CONTAINER
    
 
   HOST -.->|No direct connection| CFD_CONTAINER
 
   HOST -.->|No direct connection| MOSQ_CONTAINER

Network Isolation

Both containers run on the same Docker bridge network without exposing ports to the host:

Sources: docker-compose.yml:4-6 docker-compose.yml:11-17 README.md64


Troubleshooting Connection Issues

Token Validation Failures

If the cloudflared container fails to establish a tunnel:

  1. Verify token format: Token should be a long base64-encoded string
  2. Check.env file: Ensure CLOUDFLARE_TUNNEL_TOKEN=<token> with no extra spaces
  3. Confirm tunnel exists: Verify the tunnel is active in Cloudflare Dashboard
  4. Review logs: docker logs cloudflared will show authentication errors

DNS Resolution Failures

If cloudflared cannot reach the mosquitto container:

  1. Verify container names: Both containers must use the names defined in docker-compose.yml
  2. Check network: Both containers must be on the same Docker network (default bridge)
  3. Confirm mosquitto is running: docker ps should show both containers as “Up”
  4. Test DNS resolution: docker exec cloudflared nslookup mosquitto should resolve

Service URL Configuration

The service URL in Cloudflare Dashboard must match the Docker configuration:

  • Correct: mosquitto:9001 or http://mosquitto:9001
  • Incorrect: localhost:9001, 127.0.0.1:9001, mqtt:9001

Sources: docker-compose.yml:4-6 README.md64 README.md84

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Docker Compose Orchestration

Loading…

Docker Compose Orchestration

Relevant source files

Purpose and Scope

This document provides detailed documentation of the docker-compose.yml:1-18 file, which serves as the primary orchestration configuration for the MQTT Mosquitto Cloudflare Tunnel system. It explains how the file defines, configures, and coordinates the two core services (mosquitto and cloudflared), including their Docker images, network connectivity, volume mounts, environment variables, and lifecycle management.

For information about configuring the Mosquitto broker itself, see Mosquitto Configuration. For details about setting up the Cloudflare Tunnel token, see Environment Variables. For deployment procedures, see Deployment.


Service Composition Overview

The docker-compose.yml:1-18 file defines a minimal two-service architecture. Both services run as separate Docker containers but share a common Docker network, enabling seamless inter-container communication.

graph TB
    subgraph "docker-compose.yml"
        ComposeFile["version: '3.8'"]
ServicesBlock["services:]
    end
    
    subgraph Service Definitions"
        MosquittoService["mosquitto"]
CloudflaredService["cloudflared"]
end
    
    subgraph "mosquitto Service Configuration"
        MosquittoImage["image: eclipse-mosquitto:latest"]
MosquittoContainer["container_name: mosquitto"]
MosquittoVolumes["volumes:\n./mosquitto.conf mapped to\n/mosquitto/config/mosquitto.conf"]
MosquittoRestart["restart: unless-stopped"]
end
    
    subgraph "cloudflared Service Configuration"
        CloudflaredImage["image: cloudflare/cloudflared:latest"]
CloudflaredContainer["container_name: cloudflared"]
CloudflaredCommand["command: tunnel --no-autoupdate run\n--token ${CLOUDFLARE_TUNNEL_TOKEN}"]
CloudflaredEnv["environment:\nCLOUDFLARE_TUNNEL_TOKEN"]
CloudflaredRestart["restart: unless-stopped"]
end
    
    subgraph "Docker Runtime"
        DockerNetwork["Default Docker Network\nAuto-created"]
MosquittoRuntime["mosquitto container\nHostname: mosquitto"]
CloudflaredRuntime["cloudflared container\nHostname: cloudflared"]
end
    
 
   ComposeFile --> ServicesBlock
 
   ServicesBlock --> MosquittoService
 
   ServicesBlock --> CloudflaredService
    
 
   MosquittoService --> MosquittoImage
 
   MosquittoService --> MosquittoContainer
 
   MosquittoService --> MosquittoVolumes
 
   MosquittoService --> MosquittoRestart
    
 
   CloudflaredService --> CloudflaredImage
 
   CloudflaredService --> CloudflaredContainer
 
   CloudflaredService --> CloudflaredCommand
 
   CloudflaredService --> CloudflaredEnv
 
   CloudflaredService --> CloudflaredRestart
    
 
   MosquittoService -.->|Creates| MosquittoRuntime
 
   CloudflaredService -.->|Creates| CloudflaredRuntime
    
 
   MosquittoRuntime --> DockerNetwork
 
   CloudflaredRuntime --> DockerNetwork
    
 
   CloudflaredRuntime -.->|Proxies to mosquitto:9001| MosquittoRuntime

Service Topology

Sources : docker-compose.yml:1-18


Service Definitions

mosquitto Service

The mosquitto service definition docker-compose.yml:4-9 configures the Eclipse Mosquitto MQTT broker container.

PropertyValuePurpose
imageeclipse-mosquitto:latestSpecifies the official Eclipse Mosquitto Docker image from Docker Hub
container_namemosquittoAssigns a fixed container name for predictable hostname resolution within the Docker network
volumes./mosquitto.conf:/mosquitto/config/mosquitto.confMounts the local configuration file into the container’s expected config path
restartunless-stoppedConfigures automatic container restart on failures or host reboots, unless manually stopped

Configuration File Mount : The volume mapping docker-compose.yml8 ensures that the local mosquitto.conf file is available to the broker at runtime. The Mosquitto container automatically reads configuration from /mosquitto/config/mosquitto.conf on startup.

Image Version Strategy : The use of the latest tag docker-compose.yml5 means the system will pull the most recent stable Mosquitto release. For production deployments, consider pinning to a specific version tag (e.g., eclipse-mosquitto:2.0.18).

Sources : docker-compose.yml:4-9


cloudflared Service

The cloudflared service definition docker-compose.yml:11-18 configures the Cloudflare Tunnel client container.

PropertyValuePurpose
imagecloudflare/cloudflared:latestSpecifies the official Cloudflare Tunnel client Docker image
container_namecloudflaredAssigns a fixed container name for network identification
commandtunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}Overrides the default container command to execute the tunnel client with authentication
environmentCLOUDFLARE_TUNNEL_TOKENDeclares the environment variable that Docker Compose should inject from the .env file
restartunless-stoppedConfigures automatic container restart behavior

Command Structure : The command directive docker-compose.yml14 breaks down as follows:

tunnel                           # cloudflared subcommand
--no-autoupdate                 # Prevents automatic updates that could disrupt service
run                             # Executes the tunnel in persistent mode
--token ${CLOUDFLARE_TUNNEL_TOKEN}  # Authenticates using the token from environment variable

Environment Variable Injection : The environment section docker-compose.yml:16-17 instructs Docker Compose to read CLOUDFLARE_TUNNEL_TOKEN from the .env file (not committed to version control) and inject it into the container’s environment. The variable is then referenced in the command using standard shell variable syntax.

Sources : docker-compose.yml:11-18


graph LR
    subgraph "Docker Host"
        subgraph "Default Compose Network"
            MosquittoContainer["mosquitto\ncontainer_name: mosquitto\nAccessible as: mosquitto"]
CloudflaredContainer["cloudflared\ncontainer_name: cloudflared\nAccessible as: cloudflared"]
end
        
        HostInterface["Host Network Interface\nNot directly accessible\nfrom containers"]
end
    
    subgraph "External Network"
        CloudflareEdge["Cloudflare Edge Network"]
end
    
 
   CloudflaredContainer -->|DNS resolution: mosquitto resolves to mosquitto container IP| MosquittoContainer
 
   CloudflaredContainer -->|Outbound connection to Cloudflare| CloudflareEdge
    
 
   MosquittoContainer -.->|No direct external connectivity| HostInterface

Docker Compose Networking

Docker Compose automatically creates a default bridge network for all services defined in the same compose file. This network enables service-to-service communication using container names as hostnames.

Hostname Resolution : Because the mosquitto service has container_name: mosquitto docker-compose.yml6 the container is accessible within the Docker network using the hostname mosquitto. The Cloudflare Tunnel configuration (configured through the Cloudflare Dashboard) references mosquitto:9001 as the backend service URL, which Docker resolves to the mosquitto container’s internal IP address.

Network Isolation : Neither container exposes ports to the host network. All communication flows through:

  1. External → cloudflared : Inbound traffic arrives via the Cloudflare Tunnel (outbound-initiated connection)
  2. cloudflared → mosquitto : Internal Docker network communication on port 9001
  3. mosquitto → External : Not applicable; the broker only receives proxied traffic

No Explicit Network Declaration : The compose file does not include a networks: section, so Docker Compose uses its default behavior of creating a network named <project_name>_default (where <project_name> is typically the directory name).

Sources : docker-compose.yml:1-18


graph LR
    subgraph "Docker Host Filesystem"
        HostMosquittoConf["./mosquitto.conf\nLine: listener 1883\nLine: listener 9001\nLine: protocol websockets\nLine: allow_anonymous true"]
end
    
    subgraph "mosquitto Container"
        ContainerPath["/mosquitto/config/mosquitto.conf"]
MosquittoProcess["mosquitto process\nReads config on startup"]
end
    
 
   HostMosquittoConf -.->|Volume mount Read-only access| ContainerPath
 
   ContainerPath --> MosquittoProcess

Volume Mounts

The system uses a single volume mount to inject the Mosquitto configuration into the broker container.

Mount Specification : The volume directive docker-compose.yml:7-8 uses the syntax:

This creates a bind mount where:

  • Host path : ./mosquitto.conf (relative to the directory containing docker-compose.yml)
  • Container path : /mosquitto/config/mosquitto.conf (the default configuration location for Eclipse Mosquitto)
  • Mount type : Bind mount (inferred from the ./ prefix)
  • Read/Write mode : Read-only (implicit default for configuration files)

Configuration Reloading : Mosquitto does not automatically reload configuration changes. To apply modifications to mosquitto.conf:

  1. Edit the file on the host filesystem
  2. Restart the mosquitto container: docker compose restart mosquitto

No Data Persistence : The configuration does not define any volumes for persisting MQTT retained messages or other runtime data. If data persistence is required, additional volume mounts should be configured for /mosquitto/data.

Sources : docker-compose.yml:7-8


Restart Policies

Both services use the unless-stopped restart policy docker-compose.yml:9-15 which ensures high availability and automatic recovery from failures.

Restart Policy Behavior Matrix

ScenarioContainer BehaviorRationale
Container crashesAutomatically restartsRecovers from application failures
Docker daemon restartsAutomatically restartsMaintains service availability after host reboot
Manual docker stopDoes not restartRespects operator intent to stop service
Manual docker compose downDoes not restartStops all services as expected
Host system rebootAutomatically restarts (if Docker is configured to start on boot)Resumes services after maintenance

Alternative Restart Policies

The unless-stopped policy is appropriate for production services. Other available policies include:

  • no : Never automatically restart (requires manual intervention for failures)
  • always : Restart even after manual stop (aggressive auto-recovery)
  • on-failure : Restart only on non-zero exit codes (useful for debugging)

Configuration Example : To change the restart policy, modify docker-compose.yml9 or docker-compose.yml15:

Sources : docker-compose.yml:9-15


sequenceDiagram
    participant User
    participant DockerCompose as "docker compose"
    participant DockerEngine as "Docker Engine"
    participant EnvFile as ".env File"
    participant MosquittoConf as "mosquitto.conf File"
    participant MosquittoContainer as "mosquitto Container"
    participant CloudflaredContainer as "cloudflared Container"
    
    User->>DockerCompose: docker compose up
    DockerCompose->>EnvFile: Read environment variables
    EnvFile-->>DockerCompose: CLOUDFLARE_TUNNEL_TOKEN=xxx
    
    DockerCompose->>DockerEngine: Create network (if not exists)
    DockerEngine-->>DockerCompose: Network ready
    
    DockerCompose->>DockerEngine: Pull eclipse-mosquitto:latest
    DockerEngine-->>DockerCompose: Image ready
    
    DockerCompose->>DockerEngine: Pull cloudflare/cloudflared:latest
    DockerEngine-->>DockerCompose: Image ready
    
    DockerCompose->>DockerEngine: Create mosquitto container
    Note over DockerEngine,MosquittoConf: Volume mount ./mosquitto.conf
    DockerEngine->>MosquittoContainer: Start with mounted config
    MosquittoContainer->>MosquittoConf: Read configuration
    MosquittoContainer-->>DockerEngine: Listening on ports 1883, 9001
    
    DockerCompose->>DockerEngine: Create cloudflared container
    Note over DockerEngine,CloudflaredContainer: Inject CLOUDFLARE_TUNNEL_TOKEN
    DockerEngine->>CloudflaredContainer: Start with command:\ntunnel --no-autoupdate run --token
    CloudflaredContainer-->>DockerEngine: Tunnel established
    
    CloudflaredContainer->>MosquittoContainer: Verify mosquitto:9001 reachable
    MosquittoContainer-->>CloudflaredContainer: Connection successful
    
    DockerCompose-->>User: Services running

Service Orchestration Flow

The following diagram illustrates the complete lifecycle from docker compose up to running services.

Startup Order : Docker Compose does not guarantee a specific startup order between the two services. However, this is acceptable because:

  • The mosquitto service can start independently
  • The cloudflared service will retry connections to mosquitto:9001 if the broker is not yet ready

Dependency Management : For strict startup ordering, Docker Compose supports depends_on directives. This system does not use them because the cloudflared tunnel client includes built-in retry logic.

Sources : docker-compose.yml:1-18


Command-Line Operations

Common Docker Compose commands for managing the service stack:

Graceful Shutdown : The docker compose down command sends SIGTERM to both containers, allowing them to shut down gracefully before forcing termination.

Sources : docker-compose.yml:1-18


Service Dependency Graph

External Dependencies : The system requires:

  1. Docker Engine (to run containers)
  2. Active internet connection (for cloudflared to reach Cloudflare’s network)
  3. Valid Cloudflare Tunnel token (from Cloudflare Zero Trust dashboard)

Configuration Dependencies :

Sources : docker-compose.yml:1-18


Version Specification

The compose file uses version 3.8 docker-compose.yml1 which corresponds to Docker Compose file format 3.8, compatible with Docker Engine 19.03.0+.

Version 3.8 Features :

  • Support for init flag (not used in this configuration)
  • Enhanced credential helper support (not used in this configuration)
  • Improved service dependency controls (not used in this configuration)

Backward Compatibility : The configuration uses only basic features that are compatible with earlier 3.x versions. The version could be lowered to 3.0 without affecting functionality.

Docker Compose V2 : This configuration is compatible with both Docker Compose V1 (docker-compose) and Docker Compose V2 (docker compose). The modern V2 syntax uses docker compose (space, not hyphen).

Sources : docker-compose.yml1

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Configuration Reference

Loading…

Configuration Reference

Relevant source files

This page provides comprehensive reference documentation for all configuration files and settings in the Docker MQTT Mosquitto Cloudflare Tunnel system. Each configuration file is documented with complete syntax specifications, available directives, default values, and behavioral implications.

The system uses three configuration files:

  • docker-compose.yml - service orchestration and container definitions
  • mosquitto.conf - MQTT broker listener and access control configuration
  • .env - runtime environment variables containing sensitive credentials

For architectural context, see page 1.1. For security implications of configuration choices, see page 1.2. For setup instructions, see page 2.

Configuration Files Overview

The system uses three primary configuration files:

FilePurposeVersion ControlledContains SecretsFormat
docker-compose.ymlService orchestration and container definitionsYesNoYAML (Compose v3.8)
mosquitto.confMQTT broker configuration (listeners, protocols, access control)YesNoMosquitto INI-style
.envRuntime secrets and environment variablesNo (.gitignore)YesKEY=VALUE pairs
.env.sampleTemplate for .env fileYesNoKEY=VALUE pairs

Sources: docker-compose.yml:1-18 mosquitto.conf:1-6 .env.sample:1-3 .gitignore1

Configuration File Relationships

Configuration Directive Mapping to Runtime Behavior

The following diagram maps natural language concepts to specific configuration directives and code symbols used in the system:

Sources: docker-compose.yml:1-18 mosquitto.conf:1-6 .env.sample:1-3

graph TB
    subgraph "docker-compose.yml Directives"
        Version["version: '3.8'"]
MosqService["services.mosquitto"]
MosqImage["image: eclipse-mosquitto:latest"]
MosqContainer["container_name: mosquitto"]
MosqVolume["volumes:\n./mosquitto.conf:/mosquitto/config/mosquitto.conf"]
MosqRestart["restart: unless-stopped"]
CFService["services.cloudflared"]
CFImage["image: cloudflare/cloudflared:latest"]
CFContainer["container_name: cloudflared"]
CFCommand["command: tunnel --no-autoupdate run --token"]
CFEnv["environment:\nCLOUDFLARE_TUNNEL_TOKEN"]
CFRestart["restart: unless-stopped"]
end
    
    subgraph "mosquitto.conf Directives"
        Listener1883["listener 1883"]
AllowAnon["allow_anonymous true"]
Listener9001["listener 9001"]
ProtocolWS["protocol websockets"]
end
    
    subgraph ".env Variables"
        TunnelToken["CLOUDFLARE_TUNNEL_TOKEN=<value>"]
end
    
    subgraph "Runtime Behavior"
        TCPPort["TCP MQTT on port 1883"]
WSPort["WebSocket MQTT on port 9001"]
NoAuth["No authentication required"]
TunnelAuth["Tunnel authentication to Cloudflare"]
end
    
 
   Listener1883 --> TCPPort
 
   Listener9001 --> WSPort
 
   ProtocolWS --> WSPort
 
   AllowAnon --> NoAuth
    
 
   TunnelToken --> CFEnv
 
   CFEnv --> TunnelAuth
    
 
   MosqVolume -.->|mounts| Listener1883
 
   MosqVolume -.->|mounts| AllowAnon
 
   MosqVolume -.->|mounts| Listener9001
 
   MosqVolume -.->|mounts| ProtocolWS

docker-compose.yml Reference

The docker-compose.yml file defines the multi-container application using Docker Compose specification version 3.8. It orchestrates two services: mosquitto (MQTT broker) and cloudflared (tunnel connector).

graph TB
    Root["docker-compose.yml"]
Version["version: '3.8'\n(line 1)"]
Services["services:\n(line 3)"]
MosqService["mosquitto:\n(line 4)"]
MosqImage["image: eclipse-mosquitto:latest\n(line 5)"]
MosqContainer["container_name: mosquitto\n(line 6)"]
MosqVolumes["volumes:\n(line 7)"]
MosqVolMount["./mosquitto.conf:/mosquitto/config/mosquitto.conf\n(line 8)"]
MosqRestart["restart: unless-stopped\n(line 9)"]
CFService["cloudflared:\n(line 11)"]
CFImage["image: cloudflare/cloudflared:latest\n(line 12)"]
CFContainer["container_name: cloudflared\n(line 13)"]
CFCommand["command: tunnel --no-autoupdate run --token $CLOUDFLARE_TUNNEL_TOKEN\n(line 14)"]
CFRestart["restart: unless-stopped\n(line 15)"]
CFEnv["environment:\n(line 16)"]
CFEnvToken["- CLOUDFLARE_TUNNEL_TOKEN\n(line 17)"]
Root --> Version
 
   Root --> Services
 
   Services --> MosqService
 
   Services --> CFService
    
 
   MosqService --> MosqImage
 
   MosqService --> MosqContainer
 
   MosqService --> MosqVolumes
 
   MosqService --> MosqRestart
 
   MosqVolumes --> MosqVolMount
    
 
   CFService --> CFImage
 
   CFService --> CFContainer
 
   CFService --> CFCommand
 
   CFService --> CFRestart
 
   CFService --> CFEnv
 
   CFEnv --> CFEnvToken

File Structure

Sources: docker-compose.yml:1-18

Service: mosquitto

Directives

DirectiveValueLineDescription
imageeclipse-mosquitto:latest5Docker Hub image for Eclipse Mosquitto broker. Uses the latest tag, which tracks the most recent stable release.
container_namemosquitto6Static container name. Used by cloudflared service for DNS resolution via Docker’s internal DNS server.
volumes./mosquitto.conf:/mosquitto/config/mosquitto.conf7-8Bind mount that injects custom configuration. Host path is relative to docker-compose.yml location. Container path is the default Mosquitto config location.
restartunless-stopped9Restart policy. Container automatically restarts on failure or daemon restart, but not if manually stopped.

Sources: docker-compose.yml:4-9

Volume Mount Behavior

The volume directive at docker-compose.yml8 creates a bind mount from the host filesystem:

  • Host path: ./mosquitto.conf (relative to the directory containing docker-compose.yml)
  • Container path: /mosquitto/config/mosquitto.conf
  • Mount type: Bind mount (read-only by default in this configuration)
  • Effect: Overrides the default Mosquitto configuration with custom listener definitions

The Mosquitto process reads this file at startup. Changes to the host file require container restart to take effect.

Sources: docker-compose.yml:7-8

Service: cloudflared

Directives

DirectiveValueLineDescription
imagecloudflare/cloudflared:latest12Official Cloudflare tunnel connector image. The latest tag should be pinned to specific versions in production.
container_namecloudflared13Static container name for identification and logging.
commandtunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}14Overrides the default container entrypoint. Runs the tunnel in persistent mode with auto-update disabled. The ${CLOUDFLARE_TUNNEL_TOKEN} is substituted from the environment.
restartunless-stopped15Matches the restart policy of the mosquitto service for consistency.
environmentCLOUDFLARE_TUNNEL_TOKEN16-17Declares that the CLOUDFLARE_TUNNEL_TOKEN environment variable should be passed from the host environment to the container. Value is sourced from the .env file.

Sources: docker-compose.yml:11-18

Command Breakdown

The command directive at docker-compose.yml14 constructs the following runtime command:

tunnel --no-autoupdate run --token <token_value>

Command components:

  • tunnel - The cloudflared subcommand for tunnel operations
  • --no-autoupdate - Disables automatic updates of the cloudflared binary (recommended in containerized environments)
  • run - Starts the tunnel in persistent connection mode
  • --token - Authentication token for the tunnel (required for cloudflared tunnel connectivity)
  • ${CLOUDFLARE_TUNNEL_TOKEN} - Environment variable substitution performed by Docker Compose

Sources: docker-compose.yml14

Network Configuration

Docker Compose automatically creates a bridge network for inter-service communication. The default network name follows the pattern <project-name>_default.

Service Discovery:

  • The mosquitto service is resolvable via DNS at hostname mosquitto
  • The cloudflared service references this hostname when configured in the Cloudflare dashboard
  • No explicit networks section is required; services communicate on the default bridge network

Port Exposure:

  • Neither service exposes ports to the host (ports directive not used)
  • All external access routes through the Cloudflare Tunnel
  • Internal communication occurs on Docker’s isolated network

Sources: docker-compose.yml:1-18


graph TB
    Conf["mosquitto.conf"]
Block1["Listener Configuration 1"]
L1Port["listener 1883\n(line 1)"]
L1Anon["allow_anonymous true\n(line 2)"]
Block2["Listener Configuration 2"]
L2Port["listener 9001\n(line 4)"]
L2Proto["protocol websockets\n(line 5)"]
Conf --> Block1
 
   Conf --> Block2
    
 
   Block1 --> L1Port
 
   Block1 --> L1Anon
    
 
   Block2 --> L2Port
 
   Block2 --> L2Proto
    
    Runtime1["Runtime Effect:\nTCP MQTT on 0.0.0.0:1883\nNo authentication required"]
Runtime2["Runtime Effect:\nWebSocket MQTT on 0.0.0.0:9001\nUses same allow_anonymous setting"]
L1Port --> Runtime1
 
   L1Anon --> Runtime1
 
   L2Port --> Runtime2
 
   L2Proto --> Runtime2

mosquitto.conf Reference

The mosquitto.conf file configures the Eclipse Mosquitto MQTT broker. It defines two listeners with different protocols.

File Structure

Sources: mosquitto.conf:1-6

Listener Directives

Listener 1: TCP MQTT (Port 1883)

DirectiveValueLineDescription
listener18831Defines a listener on port 1883 (standard MQTT port). Binds to all interfaces (0.0.0.0) by default.
allow_anonymoustrue2Permits connections without username/password authentication. Applies to this listener and subsequent listeners unless overridden.

Protocol: Native MQTT (TCP-based)
Bind address: 0.0.0.0 (all interfaces, default when not specified)
Authentication: None (anonymous access enabled)
Encryption: None (plaintext). Encryption is handled by Cloudflare Tunnel at the edge.

Sources: mosquitto.conf:1-2

Listener 2: WebSocket MQTT (Port 9001)

DirectiveValueLineDescription
listener90014Defines a listener on port 9001. This port is referenced by the Cloudflare Tunnel configuration.
protocolwebsockets5Enables WebSocket protocol for this listener. Allows browser-based MQTT clients to connect.

Protocol: MQTT over WebSockets
Bind address: 0.0.0.0 (default)
Authentication: Inherits allow_anonymous true from line 2
Encryption: None at broker level (handled by Cloudflare Tunnel)

Sources: mosquitto.conf:4-5

Directive Scope and Inheritance

The allow_anonymous true directive at mosquitto.conf2 has global scope and applies to all subsequent listeners. The configuration does not explicitly set allow_anonymous for the second listener, so it inherits the global setting.

Scope hierarchy:

  1. Global directives apply to all listeners
  2. Per-listener directives override global settings
  3. Subsequent global directives override previous global directives

In this configuration:

  • Line 2 sets global anonymous access to true
  • Both listeners (lines 1 and 4) permit anonymous connections

Sources: mosquitto.conf:1-5

graph LR
    Client["MQTT Client\n(Browser or Native)"]
CFEdge["Cloudflare Edge\n(TLS termination)"]
Tunnel["Cloudflare Tunnel\n(cloudflared container)"]
Mosq9001["mosquitto:9001\n(WebSocket listener)"]
Mosq1883["mosquitto:1883\n(TCP listener)"]
Client -->|WSS/HTTPS| CFEdge
 
   CFEdge -->|Encrypted tunnel| Tunnel
 
   Tunnel -->|HTTP to mosquitto:9001| Mosq9001
    
 
   Mosq9001 -.->|Not directly accessible| Mosq1883
    
    Note1["listener 9001\nprotocol websockets\n(mosquitto.conf:4-5)"]
Note2["listener 1883\n(mosquitto.conf:1)"]
Mosq9001 --- Note1
    Mosq1883 --- Note2

Protocol Mapping to Cloudflare Tunnel

The Cloudflare Tunnel must be configured to route traffic to http://mosquitto:9001:

Critical configuration requirement: The Cloudflare Tunnel public hostname must route to http://mosquitto:9001 (the WebSocket listener), not port 1883. Port 1883 is the native MQTT protocol and cannot be proxied through HTTP-based Cloudflare Tunnels.

Sources: mosquitto.conf:1-5 docker-compose.yml:6-13

Security Implications

The allow_anonymous true directive at mosquitto.conf2 disables authentication:

Security PropertyStatusImplication
Username/password authenticationDisabledAny client can connect without credentials
Access control lists (ACLs)Not configuredAll topics are readable and writable by all clients
TLS/SSL at broker levelNot configuredPlaintext MQTT within Docker network (acceptable because traffic is encrypted by Cloudflare Tunnel before reaching the internet)

Recommended for: Development, testing, or trusted environments where the Cloudflare Tunnel provides sufficient access control.

Not recommended for: Production environments with multiple tenants or sensitive data without additional access controls.

For authentication and ACL configuration, see page 6.1.

Sources: mosquitto.conf2


Environment Variables Reference

Environment variables provide runtime configuration, particularly for sensitive credentials that must not be committed to version control.

CLOUDFLARE_TUNNEL_TOKEN

Variable name: CLOUDFLARE_TUNNEL_TOKEN
Defined in: .env (user-created, not version controlled)
Template in: .env.sample:1
Referenced in: docker-compose.yml:14,17
Used by: cloudflared service

Purpose

This variable contains the authentication token for the Cloudflare Tunnel. The token is a long-lived credential that authorizes the cloudflared container to establish an outbound connection to Cloudflare’s network and receive inbound traffic routed through the tunnel.

Token format: Base64-encoded JSON Web Token (JWT) with tunnel metadata and credentials.

Sources: .env.sample1 docker-compose.yml:14-17

Token Lifecycle

Sources: .env.sample1 docker-compose.yml:14-17

Configuration Method

The token is injected into the cloudflared container via two mechanisms:

  1. Environment variable declaration at docker-compose.yml:16-17:

This instructs Docker Compose to pass the variable from the host environment to the container.

  1. Command substitution at docker-compose.yml14:

Docker Compose performs variable substitution before starting the container, replacing ${CLOUDFLARE_TUNNEL_TOKEN} with the actual token value.

Sources: docker-compose.yml:14-17

Security Best Practices

PracticeImplementationPurpose
Never commit to version control.gitignore contains .env at line 1Prevents accidental token exposure in Git history
Use .env.sample as templateContains placeholder your_tokenProvides documentation without exposing real credentials
Restrict file permissionschmod 600 .env (not enforced by repository)Limits token access to file owner on Unix systems
Rotate tokens periodicallyRegenerate in Cloudflare dashboardLimits exposure window if token is compromised
Use separate tokens per environmentCreate distinct tunnels for dev/staging/prodLimits blast radius of token compromise

Sources: .env.sample1 .gitignore1

Validation and Troubleshooting

Token validation:

  • Token is validated when cloudflared attempts to connect to Cloudflare
  • Invalid tokens result in authentication failures logged in container output
  • No local validation occurs before container startup

Common issues:

IssueSymptomResolution
Token not setdocker-compose warning about undefined variableCreate .env file with CLOUDFLARE_TUNNEL_TOKEN=<value>
Token invalidcloudflared logs authentication errorsRegenerate token in Cloudflare dashboard
Token expiredTunnel disconnects after previously workingCheck Cloudflare dashboard for token status
Wrong tokenTunnel connects to different configurationVerify token matches intended tunnel in dashboard

Sources: docker-compose.yml:14-17 .env.sample1


sequenceDiagram
    participant DC as docker-compose
    participant Env as .env file
    participant DCFile as docker-compose.yml
    participant MConf as mosquitto.conf
    participant MosqC as mosquitto container
    participant CFC as cloudflared container
    
    Note over DC,CFC: Configuration Loading Phase
    
    DC->>Env: Read CLOUDFLARE_TUNNEL_TOKEN
    DC->>DCFile: Parse service definitions
    
    Note over DCFile: services.mosquitto (lines 4-9)
    Note over DCFile: services.cloudflared (lines 11-17)
    
    DC->>MosqC: Create container from eclipse-mosquitto:latest
    DC->>MosqC: Mount ./mosquitto.conf to /mosquitto/config/mosquitto.conf
    
    DC->>CFC: Create container from cloudflare/cloudflared:latest
    DC->>CFC: Set environment variable CLOUDFLARE_TUNNEL_TOKEN
    DC->>CFC: Set command with --token flag
    
    Note over MosqC,CFC: Container Startup Phase
    
    MosqC->>MConf: Read configuration file
    Note over MConf: listener 1883 (line 1)
    Note over MConf: allow_anonymous true (line 2)
    Note over MConf: listener 9001 (line 4)
    Note over MConf: protocol websockets (line 5)
    
    MosqC->>MosqC: Initialize listeners 1883 and 9001
    
    CFC->>CFC: Execute tunnel run with token
    CFC->>CFC: Establish connection to Cloudflare

Configuration Loading Sequence

The following diagram shows the order in which configuration is loaded and applied during system startup:

Sources: docker-compose.yml:1-18 mosquitto.conf:1-6 .env.sample:1-3

Service Configuration Matrix

The following table maps configuration sources to their effects on each service:

Configuration AspectSource FileLine(s)Applies ToEffect
MQTT TCP listenermosquitto.conf1mosquitto serviceOpens port 1883 for MQTT TCP connections
Anonymous accessmosquitto.conf2mosquitto serviceAllows connections without authentication
WebSocket listenermosquitto.conf4-5mosquitto serviceOpens port 9001 for WebSocket MQTT connections
Mosquitto imagedocker-compose.yml5mosquitto serviceUses eclipse-mosquitto:latest
Mosquitto container namedocker-compose.yml6mosquitto serviceContainer named mosquitto
Configuration mountdocker-compose.yml8mosquitto serviceMounts mosquitto.conf to /mosquitto/config/mosquitto.conf
Mosquitto restart policydocker-compose.yml9mosquitto serviceRestarts unless explicitly stopped
Cloudflared imagedocker-compose.yml12cloudflared serviceUses cloudflare/cloudflared:latest
Cloudflared container namedocker-compose.yml13cloudflared serviceContainer named cloudflared
Tunnel commanddocker-compose.yml14cloudflared serviceRuns tunnel --no-autoupdate run --token
Cloudflared restart policydocker-compose.yml15cloudflared serviceRestarts unless explicitly stopped
Tunnel token.env1cloudflared serviceAuthenticates tunnel with Cloudflare

Sources: docker-compose.yml:1-18 mosquitto.conf:1-6 .env.sample:1-3

graph TB
    Root["docker-compose.yml root"]
Version["version: '3.8'"]
Services["services:] MosqService[mosquitto:] MosqProps[Properties:\n- image\n- container_name\n- volumes\n- restart"]
CFService["cloudflared:] CFProps[Properties:\n- image\n- container_name\n- command\n- restart\n- environment"]
Root --> Version
 
   Root --> Services
 
   Services --> MosqService
 
   Services --> CFService
 
   MosqService --> MosqProps
 
   CFService --> CFProps

Configuration File Syntax

docker-compose.yml Syntax

The docker-compose.yml file follows Docker Compose version 3.8 specification. Key structural elements:

Sources: docker-compose.yml:1-18

graph LR
    Conf["mosquitto.conf"]
Block1["Listener Block 1:\nlistener 1883\nallow_anonymous true"]
Block2["Listener Block 2:\nlistener 9001\nprotocol websockets"]
Conf --> Block1
 
   Conf --> Block2
    
 
   Block1 --> Port1["Port: 1883"]
Block1 --> Anon["Anonymous: true"]
Block2 --> Port2["Port: 9001"]
Block2 --> Proto["Protocol: websockets"]

mosquitto.conf Syntax

The mosquitto.conf file uses the Mosquitto broker configuration format. Each directive appears on its own line:

Sources: mosquitto.conf:1-6

.env File Syntax

The .env file uses simple KEY=value syntax, one variable per line:

CLOUDFLARE_TUNNEL_TOKEN=your_token_here

Sources: .env.sample:1-3

Configuration Validation

The system validates configuration at multiple stages:

Validation StageComponentWhat Is ValidatedFailure Behavior
Compose file parsedocker-compose CLIYAML syntax of docker-compose.ymlExit with parse error
Environment variable resolutiondocker-compose CLIPresence of ${CLOUDFLARE_TUNNEL_TOKEN} in .envWarning or substitution with empty string
Mosquitto config parsemosquitto processSyntax of mosquitto.conf directivesContainer logs error and may exit
Tunnel authenticationcloudflared processValidity of CLOUDFLARE_TUNNEL_TOKENConnection fails, logged in container output
Port bindingDocker EngineAvailability of specified portsContainer fails to start if ports in use

Sources: docker-compose.yml:1-18 mosquitto.conf:1-6 .env.sample:1-3

Configuration Override Hierarchy

When configuration values can be specified in multiple locations, the following precedence applies:

  1. Command-line arguments to docker-compose (highest precedence)
  2. Environment variables in the shell running docker-compose
  3. .env file in the project directory
  4. Default values in docker-compose.yml
  5. Container defaults from images (lowest precedence)

For the mosquitto service, the configuration file mosquitto.conf:1-6 is mounted as a volume and overrides all default Mosquitto configurations.

Sources: docker-compose.yml:1-18 mosquitto.conf:1-6 .env.sample:1-3

Cross-Reference Index

The following table provides a quick reference for locating configuration settings:

Setting NameConfiguration FileLine Number(s)
versiondocker-compose.yml1
services.mosquitto.imagedocker-compose.yml5
services.mosquitto.container_namedocker-compose.yml6
services.mosquitto.volumesdocker-compose.yml7-8
services.mosquitto.restartdocker-compose.yml9
services.cloudflared.imagedocker-compose.yml12
services.cloudflared.container_namedocker-compose.yml13
services.cloudflared.commanddocker-compose.yml14
services.cloudflared.restartdocker-compose.yml15
services.cloudflared.environmentdocker-compose.yml16-17
listener (port 1883)mosquitto.conf1
allow_anonymousmosquitto.conf2
listener (port 9001)mosquitto.conf4
protocolmosquitto.conf5
CLOUDFLARE_TUNNEL_TOKEN.env.sample (template)1

Sources: docker-compose.yml:1-18 mosquitto.conf:1-6 .env.sample:1-3

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

docker-compose.yml

Loading…

docker-compose.yml Reference

Relevant source files

Purpose and Scope

This document provides a complete technical reference for the docker-compose.yml file, which orchestrates both the mosquitto MQTT broker and cloudflared tunnel services. It covers all configuration properties, service definitions, volume mounts, environment variables, and restart policies used in the system.

For conceptual information about how Docker Compose orchestrates the services, see page 3.3. For deployment procedures, see page 2.4. For environment variable setup, see page 4.3.

Sources: docker-compose.yml:1-18


File Location and Structure

The docker-compose.yml file is located in the repository root directory and uses Docker Compose file format version 3.8. The file defines a multi-container application with two services that work together to provide secure, externally-accessible MQTT brokering.

graph TB
    subgraph "docker-compose.yml Structure"
        Root["version: '3.8'"]
Services["services:]
        
        subgraph Service Definitions"
            Mosquitto["mosquitto:]
            Cloudflared[cloudflared:]
        end
        
        subgraph Mosquitto Properties"
            MQ_Image["image: eclipse-mosquitto:latest"]
MQ_Container["container_name: mosquitto"]
MQ_Volumes["volumes: [./mosquitto.conf mount]"]
MQ_Restart["restart: unless-stopped"]
end
        
        subgraph "Cloudflared Properties"
            CF_Image["image: cloudflare/cloudflared:latest"]
CF_Container["container_name: cloudflared"]
CF_Command["command: tunnel ... run --token"]
CF_Restart["restart: unless-stopped"]
CF_Env["environment: [CLOUDFLARE_TUNNEL_TOKEN]"]
end
    end
    
 
   Root --> Services
 
   Services --> Mosquitto
 
   Services --> Cloudflared
    
 
   Mosquitto --> MQ_Image
 
   Mosquitto --> MQ_Container
 
   Mosquitto --> MQ_Volumes
 
   Mosquitto --> MQ_Restart
    
 
   Cloudflared --> CF_Image
 
   Cloudflared --> CF_Container
 
   Cloudflared --> CF_Command
 
   Cloudflared --> CF_Restart
 
   Cloudflared --> CF_Env

High-Level Structure

Sources: docker-compose.yml:1-18


Version Specification

PropertyValueLine
version'3.8'docker-compose.yml1

The file uses Docker Compose file format version 3.8, which supports all features required by this system including service definitions, volume mounts, environment variables, and restart policies. Version 3.8 is compatible with Docker Engine 19.03.0+ and provides the necessary capabilities for container orchestration.

Sources: docker-compose.yml1


Services Overview

The services section defines two containerized services that comprise the complete system. Both services run continuously and restart automatically on failure.

graph LR
    subgraph "Docker Compose Services"
        subgraph "mosquitto Service"
            MQ_Runtime["Container: mosquitto"]
MQ_Base["Base Image: eclipse-mosquitto:latest"]
MQ_Config["Config Volume Mount"]
MQ_Policy["Restart: unless-stopped"]
end
        
        subgraph "cloudflared Service"
            CF_Runtime["Container: cloudflared"]
CF_Base["Base Image: cloudflare/cloudflared:latest"]
CF_Token["Environment: CLOUDFLARE_TUNNEL_TOKEN"]
CF_Cmd["Command: tunnel run"]
CF_Policy["Restart: unless-stopped"]
end
    end
    
    subgraph "External Resources"
        MosqConf["./mosquitto.conf"]
EnvFile[".env File"]
end
    
 
   MosqConf -.-> MQ_Config
 
   EnvFile -.-> CF_Token
    
 
   CF_Runtime -->|Proxies to| MQ_Runtime

Service Composition Diagram

Sources: docker-compose.yml:3-18


Mosquitto Service Configuration

The mosquitto service runs the Eclipse Mosquitto MQTT broker. It is defined starting at line 4 and includes image specification, container naming, volume configuration, and restart policy.

Service Properties

PropertyValueLineDescription
imageeclipse-mosquitto:latestdocker-compose.yml5Official Eclipse Mosquitto Docker image, latest version
container_namemosquittodocker-compose.yml6Fixed container name for internal DNS resolution
volumes./mosquitto.conf:/mosquitto/config/mosquitto.confdocker-compose.yml:7-8Mounts local config file into container
restartunless-stoppeddocker-compose.yml9Automatic restart policy

Image Specification

docker-compose.yml5 specifies eclipse-mosquitto:latest as the base image. This pulls the official Mosquitto image from Docker Hub. The latest tag ensures the most recent stable version is used, though this can be pinned to a specific version for production deployments.

Container Name

docker-compose.yml6 sets a fixed container name mosquitto. This serves two purposes:

  1. Provides predictable DNS resolution within Docker’s internal network
  2. Enables the cloudflared service to reference the broker at mosquitto:9001

Volume Mount Configuration

docker-compose.yml:7-8 configures a bind mount that makes the broker configuration available to the container:

volumes:
  - ./mosquitto.conf:/mosquitto/config/mosquitto.conf
ComponentValueDescription
Host Path./mosquitto.confRelative path from docker-compose.yml location
Container Path/mosquitto/config/mosquitto.confStandard Mosquitto config location
Mount TypeBind mountDirect filesystem mapping
graph LR
    HostFS["Host Filesystem\n./mosquitto.conf"]
Mount["Volume Mount\nBind Type"]
ContainerFS["Container Filesystem\n/mosquitto/config/mosquitto.conf"]
Process["mosquitto Process\nReads Config"]
HostFS -->|Mounted by Docker| Mount
 
   Mount -->|Accessible at| ContainerFS
 
   ContainerFS -->|Read by| Process

This mount allows configuration changes without rebuilding the container image. The mosquitto process reads this configuration on startup.

Volume Mount Flow

Sources: docker-compose.yml:4-9


Cloudflared Service Configuration

The cloudflared service establishes a secure tunnel to Cloudflare’s network. It is defined starting at line 11 and includes image specification, container naming, command configuration, environment variables, and restart policy.

Service Properties

PropertyValueLineDescription
imagecloudflare/cloudflared:latestdocker-compose.yml12Official Cloudflare Tunnel client image
container_namecloudflareddocker-compose.yml13Fixed container name
commandtunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}docker-compose.yml14Command with token interpolation
restartunless-stoppeddocker-compose.yml15Automatic restart policy
environmentCLOUDFLARE_TUNNEL_TOKENdocker-compose.yml:16-17Environment variable declaration

Image Specification

docker-compose.yml12 specifies cloudflare/cloudflared:latest as the base image. This is the official Cloudflare Tunnel client maintained by Cloudflare.

Command Override

docker-compose.yml14 overrides the default container command with:

tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}
ArgumentPurpose
tunnelMain cloudflared subcommand for tunnel operations
--no-autoupdateDisables automatic updates within container (container restart handles updates)
runStarts the tunnel using token authentication
--tokenSpecifies authentication token (value from environment variable)
${CLOUDFLARE_TUNNEL_TOKEN}Environment variable interpolation by Docker Compose

Environment Variables

docker-compose.yml:16-17 declares the environment variable section:

graph TB
    EnvFile[".env File\nCLOUDFLARE_TUNNEL_TOKEN=xxx"]
Compose["docker-compose.yml\nEnvironment Declaration"]
Runtime["Container Runtime Environment\nCLOUDFLARE_TUNNEL_TOKEN=xxx"]
Command["Command Execution\ntunnel run --token xxx"]
EnvFile -->|Read by docker-compose| Compose
 
   Compose -->|Passes to container| Runtime
 
   Runtime -->|Interpolated in| Command
environment:
  - CLOUDFLARE_TUNNEL_TOKEN

This configuration tells Docker Compose to pass the CLOUDFLARE_TUNNEL_TOKEN environment variable from the host (typically loaded from .env file) into the container. The variable’s value is then used in the command string via ${CLOUDFLARE_TUNNEL_TOKEN} interpolation.

Environment Variable Flow

Sources: docker-compose.yml:11-17


Restart Policy Configuration

Both services use restart: unless-stopped docker-compose.yml:9-15 This restart policy ensures containers automatically restart under most failure conditions but respect explicit stop commands.

Restart Policy Behavior

ScenarioBehavior
Container exits with errorAutomatically restarts
Container exits successfully (exit code 0)Automatically restarts
Docker daemon restartsContainer restarts
Manual docker stopContainer remains stopped
Manual docker-compose downContainer remains stopped
System rebootContainer does NOT restart (requires Docker to start stopped containers)

Alternative Restart Policies

While this system uses unless-stopped, Docker Compose supports other policies:

PolicyDescriptionUse Case
noNever restart (default)Development, debugging
alwaysAlways restart, even after manual stopMaximum availability
unless-stoppedRestart unless explicitly stoppedRecommended for most services
on-failure[:max-retries]Restart only on non-zero exitServices with proper shutdown

Sources: docker-compose.yml:9-15


graph TB
    subgraph "Docker Default Bridge Network"
        subgraph "cloudflared Container"
            CF_Process["cloudflared process"]
CF_DNS["DNS Resolution:\nmosquitto resolves to container IP"]
end
        
        subgraph "mosquitto Container"
            MQ_Process["mosquitto process"]
MQ_Listener["Listener: 0.0.0.0:9001"]
end
        
 
       CF_Process -->|HTTP Proxy to mosquitto:9001| CF_DNS
 
       CF_DNS -->|Resolves via Docker DNS| MQ_Listener
    end
    
    External["External Traffic\nfrom Cloudflare"]
External -.->|Via Tunnel| CF_Process

Service-to-Service Communication

Although not explicitly defined in the compose file, Docker Compose creates a default network for all services, enabling internal communication.

Internal Networking Model

The cloudflared service proxies traffic to mosquitto:9001, where mosquitto is resolved via Docker’s internal DNS to the mosquitto container’s IP address. This is made possible by the fixed container_name properties.

Sources: docker-compose.yml:6-13


Complete Property Reference

Top-Level Properties

PropertyTypeRequiredValue in File
versionstringNo (but recommended)'3.8'
servicesobjectYesContains mosquitto and cloudflared

Mosquitto Service Properties

Property PathTypeRequiredValueDefault if Omitted
services.mosquitto.imagestringYeseclipse-mosquitto:latestN/A
services.mosquitto.container_namestringNomosquittoAuto-generated
services.mosquitto.volumesarrayNo["./mosquitto.conf:/mosquitto/config/mosquitto.conf"]No volumes
services.mosquitto.restartstringNounless-stoppedno

Cloudflared Service Properties

Property PathTypeRequiredValueDefault if Omitted
services.cloudflared.imagestringYescloudflare/cloudflared:latestN/A
services.cloudflared.container_namestringNocloudflaredAuto-generated
services.cloudflared.commandstring/arrayNotunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}Image default
services.cloudflared.restartstringNounless-stoppedno
services.cloudflared.environmentarray/objectNo["CLOUDFLARE_TUNNEL_TOKEN"]No environment vars

Sources: docker-compose.yml:1-18


Deployment Commands

The following commands operate on this docker-compose.yml file:

CommandPurpose
docker-compose up -dStart all services in detached mode
docker-compose downStop and remove all containers
docker-compose psShow service status
docker-compose logsView combined logs
docker-compose logs mosquittoView mosquitto logs only
docker-compose logs cloudflaredView cloudflared logs only
docker-compose restartRestart all services
docker-compose pullPull latest images

Sources: docker-compose.yml:1-18


File Modification Considerations

When modifying docker-compose.yml, consider the following:

Image Version Pinning

The current configuration uses latest tags docker-compose.yml:5-12 For production environments, consider pinning specific versions:

Network Isolation

To add explicit network configuration:

Port Exposure

The current configuration does not expose ports to the host, which is intentional (all traffic goes through Cloudflare Tunnel). To expose ports for testing:

⚠️ Security Warning: Exposing ports bypasses the Cloudflare Tunnel security layer and should only be done in controlled environments.

Sources: docker-compose.yml:1-18


Configuration Validation

To validate the docker-compose.yml syntax:

This command parses the file, interpolates environment variables, and displays the resolved configuration. It will report syntax errors and missing environment variables.

Sources: docker-compose.yml:1-18

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

mosquitto.conf

Loading…

mosquitto.conf Reference

Relevant source files

Purpose and Scope

This page provides complete technical reference documentation for the mosquitto.conf configuration file used in this system. The file defines the MQTT broker’s runtime behavior, including network listeners, protocol support, and access control settings.

For conceptual information about Mosquitto configuration and setup procedures, see Mosquitto Configuration. For detailed documentation of the Mosquitto broker component itself, see Mosquitto MQTT Broker. For listener-specific implementation details, see MQTT Listeners and Anonymous Access.

Sources: mosquitto.conf:1-6


File Location and Integration

The mosquitto.conf file resides in the project root directory and is mounted as a read-only volume into the mosquitto container at runtime. The file is version-controlled and contains no secrets.

graph TB
    subgraph "Version Control"
        MosqConf["mosquitto.conf\n(Project Root)"]
end
    
    subgraph "Docker Compose"
        ComposeYML["docker-compose.yml"]
VolumeMount["Volume Mount Definition\n./mosquitto.conf:/mosquitto/config/mosquitto.conf:ro"]
end
    
    subgraph "Container Runtime"
        MosqContainer["mosquitto Container\n(eclipse-mosquitto:latest)"]
MosqProcess["mosquitto Process\nReads Config at Startup"]
end
    
 
   MosqConf --> ComposeYML
 
   ComposeYML --> VolumeMount
 
   VolumeMount --> MosqContainer
 
   MosqContainer --> MosqProcess

Docker Compose Integration

Diagram: mosquitto.conf Mount and Load Path

The configuration is mounted read-only (:ro flag) to prevent the container from modifying the configuration file. The mosquitto process reads this configuration file during container initialization.

Sources: mosquitto.conf:1-6 docker-compose.yml


Configuration Overview

The current mosquitto.conf implements a minimal configuration optimized for accessibility and simplicity. It defines two network listeners with different protocols and enables anonymous client connections.

graph TB
    subgraph "mosquitto.conf"
        RootConfig["Configuration Root"]
subgraph "First Listener Block"
            Listener1["listener 1883"]
Anonymous["allow_anonymous true"]
end
        
        subgraph "Second Listener Block"
            Listener2["listener 9001"]
Protocol["protocol websockets"]
end
        
 
       RootConfig --> Listener1
 
       Listener1 --> Anonymous
        
 
       RootConfig --> Listener2
 
       Listener2 --> Protocol
    end
    
    subgraph "Runtime Behavior"
        Port1883["TCP Port 1883\nStandard MQTT Protocol"]
Port9001["TCP Port 9001\nMQTT over WebSockets"]
AnonymousAccess["All Clients Accepted\nNo Authentication Required"]
end
    
 
   Listener1 --> Port1883
 
   Listener2 --> Port9001
 
   Anonymous --> AnonymousAccess

Active Configuration Structure

Diagram: Configuration Structure and Runtime Mapping

Sources: mosquitto.conf:1-6


Directive Reference

listener

Syntax: listener <port> [<bind_address>]

Purpose: Defines a network listener that accepts incoming MQTT client connections.

Parameters:

ParameterRequiredDescription
portYesTCP port number (1-65535) on which to listen for connections
bind_addressNoIP address or hostname to bind to (default: all interfaces)

Behavior:

  • Multiple listener directives create multiple independent listeners
  • Each listener can be configured with different protocols and security settings
  • Configuration directives following a listener apply to that specific listener until the next listener directive
  • When no bind address is specified, the listener binds to all available network interfaces (0.0.0.0)

Current Usage:

mosquitto.conf1 defines the first listener:

listener 1883

This creates a listener on port 1883 (the IANA-registered standard MQTT port) bound to all interfaces.

mosquitto.conf4 defines the second listener:

listener 9001

This creates a listener on port 9001 (commonly used for MQTT WebSockets) bound to all interfaces.

Sources: mosquitto.conf1 mosquitto.conf4


allow_anonymous

Syntax: allow_anonymous <true|false>

Purpose: Controls whether clients can connect without providing authentication credentials.

Parameters:

ParameterRequiredValid ValuesDescription
SettingYestrue or falseEnable or disable anonymous connections

Behavior:

  • When set to true: Clients may connect without providing a username or password
  • When set to false: All clients must authenticate using username/password or certificate-based authentication
  • This setting applies to all listeners unless overridden by listener-specific configuration
  • Default value: false (as of Mosquitto 2.0+)

Security Implications:

  • Anonymous access is appropriate for trusted networks or development environments
  • Production deployments should typically disable anonymous access and implement authentication
  • When anonymous access is enabled, no per-client authorization checks occur unless ACLs are configured

Current Usage:

mosquitto.conf2 enables anonymous access:

allow_anonymous true

This configuration allows any client to connect to either listener (port 1883 or port 9001) without authentication.

Sources: mosquitto.conf2


protocol

Syntax: protocol <protocol_type>

Purpose: Specifies the MQTT protocol variant for a specific listener.

Parameters:

ParameterRequiredValid ValuesDescription
protocol_typeYesmqtt, websocketsProtocol encapsulation method

Behavior:

  • Must be specified within a listener block (after a listener directive)
  • Applies only to the immediately preceding listener
  • When set to mqtt: Standard MQTT protocol over TCP (default behavior)
  • When set to websockets: MQTT protocol encapsulated in WebSocket frames

Protocol Variants:

ValueDescriptionUse Case
mqttStandard MQTT over TCPTraditional MQTT clients, IoT devices, backend services
websocketsMQTT over WebSocket protocolWeb browsers, JavaScript clients, environments with HTTP-only connectivity

WebSocket Protocol Details:

  • Enables MQTT communication from browser-based JavaScript clients
  • WebSocket frames provide HTTP-compatible upgrade mechanism
  • Allows MQTT traffic to traverse HTTP proxies and firewalls
  • Implements RFC 6455 (WebSocket Protocol)

Current Usage:

mosquitto.conf5 configures the 9001 listener for WebSockets:

protocol websockets

This enables MQTT-over-WebSocket support on port 9001, while port 1883 uses standard MQTT (default when protocol is not specified).

Sources: mosquitto.conf5


graph LR
    subgraph "mosquitto.conf Directives"
        Line1["listener 1883\n(Line 1)"]
Line2["allow_anonymous true\n(Line 2)"]
Line4["listener 9001\n(Line 4)"]
Line5["protocol websockets\n(Line 5)"]
end
    
    subgraph "Listener 1 Runtime Config"
        L1Port["Bind Port: 1883"]
L1Protocol["Protocol: mqtt (default)"]
L1Auth["Auth: Anonymous Allowed"]
end
    
    subgraph "Listener 2 Runtime Config"
        L2Port["Bind Port: 9001"]
L2Protocol["Protocol: websockets"]
L2Auth["Auth: Anonymous Allowed (inherited)"]
end
    
 
   Line1 --> L1Port
 
   Line1 --> L1Protocol
 
   Line2 --> L1Auth
 
   Line2 --> L2Auth
    
 
   Line4 --> L2Port
 
   Line5 --> L2Protocol

Complete Configuration Map

The following diagram shows how each directive in mosquitto.conf maps to runtime broker behavior:

Diagram: Directive-to-Runtime Configuration Mapping

Sources: mosquitto.conf:1-6


Configuration Directives Not Present

The current configuration uses only three directives. Mosquitto supports numerous additional directives for advanced functionality. Common directives not present in this configuration include:

Authentication and Authorization

DirectivePurpose
password_filePath to file containing username/password credentials
acl_filePath to file defining topic-based access control rules
allow_zero_length_clientidControl client ID requirements

TLS/SSL Configuration

DirectivePurpose
cafileCA certificate for client certificate validation
certfileServer certificate for TLS
keyfileServer private key for TLS
require_certificateRequire client certificates

Persistence and Logging

DirectivePurpose
persistenceEnable message persistence to disk
persistence_locationDirectory for persistence database
log_destLogging destination (file, stdout, syslog)
log_typeTypes of messages to log

Connection Limits

DirectivePurpose
max_connectionsMaximum concurrent client connections
max_keepaliveMaximum keepalive interval
max_packet_sizeMaximum MQTT packet size

For advanced configuration including ACL-based access control, see the protected-no-wildcard branch documentation at Topic Access Control (ACL).

Sources: mosquitto.conf:1-6


graph TB
    subgraph "Configuration Layer"
        MosqConf["mosquitto.conf"]
ComposeYML["docker-compose.yml"]
EnvFile[".env"]
end
    
    subgraph "Container Layer"
        MosqContainer["mosquitto container\n(eclipse-mosquitto:latest)"]
CFDContainer["cloudflared container"]
end
    
    subgraph "Mosquitto Configuration Application"
        Listener1883["Listener: Port 1883\nProtocol: mqtt\nFrom: mosquitto.conf:1"]
Listener9001["Listener: Port 9001\nProtocol: websockets\nFrom: mosquitto.conf:4-5"]
AuthPolicy["Anonymous Access: Enabled\nFrom: mosquitto.conf:2"]
end
    
    subgraph "Traffic Flow"
        CFProxy["cloudflared proxies to\nmosquitto:9001"]
ExternalClients["External MQTT Clients\n(via Cloudflare Tunnel)"]
InternalClients["Internal MQTT Clients\n(direct to port 1883)"]
end
    
 
   MosqConf -->|Volume Mount| MosqContainer
 
   ComposeYML -->|Orchestrates| MosqContainer
 
   ComposeYML -->|Orchestrates| CFDContainer
 
   EnvFile -->|Provides Token to| CFDContainer
    
 
   MosqContainer --> Listener1883
 
   MosqContainer --> Listener9001
 
   MosqContainer --> AuthPolicy
    
 
   CFProxy -->|WebSocket Connection| Listener9001
 
   ExternalClients -->|Through Cloudflare| CFProxy
 
   InternalClients -->|Direct TCP| Listener1883
    
 
   AuthPolicy -.->|Applies to| Listener1883
 
   AuthPolicy -.->|Applies to| Listener9001

Relationship to System Architecture

The mosquitto.conf file configures the MQTT broker component within the larger system architecture:

Diagram: mosquitto.conf in System Context

The configuration file directly controls broker behavior, while Docker Compose handles orchestration and the Cloudflare tunnel handles external routing. Note that cloudflared proxies specifically to port 9001 (WebSocket listener), making this the primary entry point for external clients.

Sources: mosquitto.conf:1-6 docker-compose.yml


Configuration Modification Guidelines

Modifying the Configuration

  1. Edit mosquitto.conf in the project root directory

  2. Restart the mosquitto container to apply changes:

  3. Verify configuration by checking container logs:

Common Configuration Changes

Disabling Anonymous Access:

allow_anonymous false
password_file /mosquitto/config/password.txt

Enabling TLS on WebSocket Listener:

listener 9001
protocol websockets
cafile /mosquitto/config/ca.crt
certfile /mosquitto/config/server.crt
keyfile /mosquitto/config/server.key

Adding Standard MQTT over TLS:

listener 8883
cafile /mosquitto/config/ca.crt
certfile /mosquitto/config/server.crt
keyfile /mosquitto/config/server.key

Note that any additional files (passwords, certificates, ACLs) must be mounted into the container via additional volume mounts in docker-compose.yml.

Sources: mosquitto.conf:1-6 docker-compose.yml


Validation and Testing

After modifying mosquitto.conf, validate the configuration before deployment:

Syntax Validation

The mosquitto process validates configuration on startup. Invalid configurations will prevent container startup. Check logs for validation errors:

Configuration errors appear in startup logs with the format:

Error: <description of error>

Testing Listener Configuration

Test each configured listener independently:

Port 1883 (Standard MQTT):

Port 9001 (WebSockets): Requires a WebSocket-capable MQTT client. Browser-based clients can connect to:

ws://localhost:9001

For testing WebSocket connectivity through the Cloudflare tunnel, use the public hostname configured in the Cloudflare dashboard.

Sources: mosquitto.conf:1-6


File Format Specifications

Syntax Rules

RuleDescription
Line-basedEach directive appears on a separate line
CommentsLines starting with # are ignored
WhitespaceLeading and trailing whitespace is ignored
Blank linesEmpty lines are ignored
Case sensitivityDirective names are case-sensitive
ContinuationNo multi-line continuation support

Directive Scope

Configuration directives fall into two categories:

Global Directives:

  • Apply to the entire broker instance
  • Examples: allow_anonymous, persistence, log_dest
  • Typically appear before any listener directives

Listener-Specific Directives:

  • Apply only to the immediately preceding listener
  • Examples: protocol, cafile, certfile
  • Must follow a listener directive

In mosquitto.conf:1-6 allow_anonymous is a global directive (line 2) that applies to all listeners, while protocol is listener-specific (line 5) and applies only to the port 9001 listener.

Sources: mosquitto.conf:1-6

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Environment Variables

Loading…

Environment Variables

Relevant source files

Purpose and Scope

This document provides a comprehensive reference for all environment variables used in the Docker MQTT Mosquitto with Cloudflare Tunnel system. It covers the .env file structure, the .env.sample template, and security practices for managing sensitive credentials.

For information about the Docker Compose configuration that consumes these variables, see docker-compose.yml. For the initial setup process of configuring environment variables, see Local Configuration. For version control practices related to secret management, see Version Control Best Practices.

Environment Variable System Overview

The system uses a file-based environment variable pattern where secrets are stored in a .env file that is explicitly excluded from version control. A template file, .env.sample, is committed to the repository to document the required variables without exposing actual values.

File Structure

project-root/
├── .env                  # Actual secrets (gitignored)
├── .env.sample           # Template (version controlled)
├── .gitignore           # Excludes .env
└── docker-compose.yml   # Consumes environment variables

Sources : .env.sample1 .gitignore1

Environment Variable File Relationship

Analysis : This diagram shows the critical separation between template and actual secrets. The .env.sample file serves as documentation in version control, while .env contains actual credentials and is explicitly excluded by .gitignore. Developers copy the template and populate it with real values obtained from the Cloudflare dashboard.

Sources : .env.sample1 .gitignore1

CLOUDFLARE_TUNNEL_TOKEN

The system uses a single environment variable for authentication and configuration.

Variable Specification

Variable NameRequiredTypeDescription
CLOUDFLARE_TUNNEL_TOKENYesString (JWT)Authentication token generated by Cloudflare Zero Trust dashboard for tunnel access

Sources : .env.sample1

Purpose and Function

The CLOUDFLARE_TUNNEL_TOKEN is a JSON Web Token (JWT) that contains:

  • Tunnel identification information
  • Authentication credentials
  • Routing configuration
  • Cryptographic signatures

This token authorizes the cloudflared container to establish an outbound connection to Cloudflare’s network and route traffic to the local Mosquitto broker.

Token Format

The token is a base64-encoded JWT string with the following characteristics:

  • Length: Approximately 500-800 characters
  • Format: eyJ... (standard JWT prefix)
  • Components: Header, payload, and signature concatenated with dots

Example from template :

CLOUDFLARE_TUNNEL_TOKEN=your_token

Sources : .env.sample1

Obtaining the Token

The token is generated through the Cloudflare Zero Trust dashboard during tunnel creation. See Cloudflare Tunnel Setup for detailed instructions. The token should be copied from the dashboard and placed in the .env file.

sequenceDiagram
    participant User as "User"
    participant Dashboard as "Cloudflare Dashboard"
    participant EnvSample as ".env.sample"
    participant EnvFile as ".env (local)"
    participant Compose as "docker-compose.yml"
    participant Container as "cloudflared container"
    participant Tunnel as "Cloudflare Tunnel Service"
    
    User->>Dashboard: 1. Create tunnel
    Dashboard-->>User: 2. Generate CLOUDFLARE_TUNNEL_TOKEN
    
    User->>EnvSample: 3. Read template
    EnvSample-->>User: CLOUDFLARE_TUNNEL_TOKEN=your_token
    
    User->>EnvFile: 4. Create .env file
    User->>EnvFile: 5. Paste actual token
    
    Note over Compose: docker-compose up
    
    Compose->>EnvFile: 6. Read environment variables
    EnvFile-->>Compose: CLOUDFLARE_TUNNEL_TOKEN=eyJh...
    
    Compose->>Container: 7. Start container with env var
    Container->>Container: 8. cloudflared tunnel run
    Container->>Tunnel: 9. Authenticate with token
    Tunnel-->>Container: 10. Tunnel established
    
    Note over Container,Tunnel: Persistent outbound connection\nmaintained for traffic routing

Token Lifecycle and Usage

Analysis : The token is generated once during tunnel creation and remains valid until the tunnel is deleted or the token is rotated. The token is read from .env at container startup and used by the cloudflared tunnel run command to authenticate with Cloudflare’s network.

Sources : .env.sample1

Security Architecture

graph LR
    subgraph "Files That Can Be Committed"
        SAMPLE[".env.sample\nTemplate only"]
README["README.md"]
COMPOSE["docker-compose.yml"]
MOSQUITTO["mosquitto.conf"]
GITIGNORE_FILE[".gitignore"]
end
    
    subgraph "Files That Must Not Be Committed"
        ENV[".env\nContains actual token"]
DATA["data/*\nMosquitto persistence"]
end
    
    subgraph ".gitignore Rules"
        RULE1["Line 1: .env"]
RULE2["Line 2: data/*"]
end
    
 
   RULE1 -.->|excludes| ENV
 
   RULE2 -.->|excludes| DATA
    
 
   SAMPLE -.->|safe to commit| README
 
   ENV -.->|blocked by gitignore| SAMPLE

The .gitignore Boundary

The .gitignore file establishes a security boundary that prevents accidental exposure of secrets to version control.

Analysis : The .gitignore file contains two critical rules. Line 1 excludes .env to prevent token exposure. Line 2 excludes data/* to prevent committing Mosquitto’s persistence data, which may contain message history or subscriber information.

Sources : .gitignore:1-2

File Contents and Security Model

.env.sample (Template File)

Located at .env.sample1 this file provides:

  • Documentation of required variables

  • Example structure (not functional values)

  • Onboarding guide for new developers

    CLOUDFLARE_TUNNEL_TOKEN=your_token

The value your_token is a placeholder and will not work for authentication. Developers must replace it with an actual token from the Cloudflare dashboard.

Security Properties :

  • Safe to commit to public repositories
  • Contains no sensitive information
  • Serves as self-documenting configuration reference

.env (Actual Secrets File)

Not present in the repository; created locally by developers. Contains:

  • Real CLOUDFLARE_TUNNEL_TOKEN value
  • Full JWT string with authentication credentials

Security Properties :

  • Excluded from version control via .gitignore1
  • Should have restrictive file permissions (chmod 600 .env)
  • Must never be shared publicly or committed to Git

Sources : .env.sample1 .gitignore1

graph TB
    subgraph "Filesystem"
        ENV_FILE[".env file"]
end
    
    subgraph "Docker Compose Process"
        COMPOSE_CMD["docker-compose up"]
COMPOSE_PARSER["YAML Parser"]
COMPOSE_ENGINE["Docker Engine API"]
end
    
    subgraph "docker-compose.yml"
        SERVICE_DEF["cloudflared service definition"]
ENV_REF["environment:\n - CLOUDFLARE_TUNNEL_TOKEN"]
end
    
    subgraph "Container Runtime"
        CONTAINER["cloudflared container"]
PROCESS["cloudflared tunnel run"]
ENV_VAR["CLOUDFLARE_TUNNEL_TOKEN\nenvironment variable"]
end
    
 
   ENV_FILE -->|1. read automatically| COMPOSE_CMD
 
   COMPOSE_CMD --> COMPOSE_PARSER
 
   COMPOSE_PARSER -->|2. parse service config| SERVICE_DEF
 
   SERVICE_DEF --> ENV_REF
 
   ENV_REF -->|3. resolve variable| ENV_FILE
 
   ENV_FILE -->|4. substitute value| COMPOSE_ENGINE
 
   COMPOSE_ENGINE -->|5. create container with env| CONTAINER
 
   CONTAINER -->|6. start process| PROCESS
 
   PROCESS -->|7. read env var| ENV_VAR

Integration with Docker Compose

The docker-compose.yml file consumes environment variables from the .env file automatically through Docker Compose’s built-in environment file support.

Environment Variable Injection Flow

Analysis : Docker Compose automatically loads the .env file from the project root directory. When it encounters environment variable references in docker-compose.yml, it substitutes values from the .env file before passing them to the Docker Engine. The cloudflared process can then access CLOUDFLARE_TUNNEL_TOKEN as a standard environment variable.

Sources : .env.sample1

graph LR
    subgraph "Environment Variables"
        TOKEN["CLOUDFLARE_TUNNEL_TOKEN"]
end
    
    subgraph "Containers"
        CFD["cloudflared\n✓ Has access"]
MOSQ["mosquitto\n✗ No access"]
end
    
 
   TOKEN -->|injected via environment| CFD
 
   TOKEN -.->|not available| MOSQ

Variable Scoping

The CLOUDFLARE_TUNNEL_TOKEN is only available to the cloudflared container. The mosquitto container does not have access to this variable, demonstrating principle of least privilege.

Sources : .env.sample1

Best Practices

Local Development

  1. Initial Setup :

  2. File Permissions :

Restricts read/write access to the file owner only.

  1. Verification : Before starting containers, verify the token is populated:

If this returns empty, the token has not been configured.

Production Deployment

For production environments, consider alternatives to file-based secrets:

MethodSecurityRotationAudit
.env fileLowManualNo
Docker secretsMediumManualLimited
HashiCorp VaultHighAutomatedYes
AWS Secrets ManagerHighAutomatedYes
Azure Key VaultHighAutomatedYes

Production deployments should:

  • Use secrets management systems instead of .env files
  • Implement automatic token rotation
  • Enable audit logging for secret access
  • Use read-only access where possible
graph TB
    subgraph "Common Mistakes (DO NOT DO)"
        COMMIT["❌ Committing .env to Git"]
HARDCODE["❌ Hardcoding token in docker-compose.yml"]
PUBLIC["❌ Posting token in issues/forums"]
SHARE["❌ Sharing .env file via email/chat"]
PERMISSIONS["❌ World-readable .env file"]
end
    
    subgraph "Correct Practices (DO THIS)"
        GITIGNORE_CHECK["✓ Verify .env in .gitignore"]
ENV_PATTERN["✓ Use environment variable pattern"]
ROTATE["✓ Rotate tokens regularly"]
RESTRICT["✓ Restrict file permissions"]
SECRETS_MGR["✓ Use secrets manager in production"]
end
    
 
   COMMIT -.->|leads to| TOKEN_LEAK["Token Exposure"]
HARDCODE -.->|leads to| TOKEN_LEAK
 
   PUBLIC -.->|leads to| TOKEN_LEAK
 
   SHARE -.->|leads to| TOKEN_LEAK
 
   PERMISSIONS -.->|leads to| TOKEN_LEAK
    
 
   TOKEN_LEAK -.->|enables| UNAUTHORIZED["Unauthorized Access"]

Token Rotation

The CLOUDFLARE_TUNNEL_TOKEN can be rotated by:

  1. Creating a new tunnel in the Cloudflare dashboard
  2. Updating the .env file with the new token
  3. Restarting containers: docker-compose restart cloudflared

Common Security Mistakes

Sources : .gitignore1

Troubleshooting

Token Not Found Error

Symptom : cloudflared container fails to start with error about missing token.

Diagnosis :

  1. Verify .env file exists in project root
  2. Check token variable name is exactly CLOUDFLARE_TUNNEL_TOKEN
  3. Ensure no extra spaces or quotes around the token value

Solution : Create or correct the .env file using .env.sample1 as template.

Token Authentication Failed

Symptom : cloudflared container starts but fails to establish tunnel.

Diagnosis :

  1. Token may be invalid or expired
  2. Token may be from a different tunnel
  3. Tunnel may have been deleted in Cloudflare dashboard

Solution : Generate new token from Cloudflare dashboard and update .env file.

.env File Accidentally Committed

Symptom : .env file appears in Git history.

Immediate Actions :

  1. Rotate token immediately in Cloudflare dashboard
  2. Remove file from Git history using git filter-branch or BFG Repo-Cleaner
  3. Update .env with new token
  4. Verify .gitignore1 contains .env

Environment Variable Reference Table

VariableTypeRequiredDefaultContainerUsage
CLOUDFLARE_TUNNEL_TOKENString (JWT)YesNonecloudflaredAuthenticates tunnel connection to Cloudflare network

Sources : .env.sample1


Sources : .env.sample1 .gitignore:1-2

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

CI/CD and Automation

Loading…

CI/CD and Automation

Relevant source files

Purpose and Scope

This document describes the automated testing and documentation build systems that ensure code quality and maintain up-to-date documentation for the Docker MQTT Mosquitto Cloudflare Tunnel system.

The system implements two distinct automation pipelines:

  1. Continuous Integration Pipeline - Validates that the Mosquitto service can successfully start on every commit
  2. Documentation Build Pipeline - Generates and deploys wiki documentation to GitHub Pages

This page is organized into three main sections:

  • Continuous Integration (Section 5.1) - Details the automated testing workflow
  • Documentation Pipeline (Section 5.2) - Explains the documentation generation and deployment process
  • Version Control Best Practices (Section 5.3) - Covers Git workflow and secret management in the context of CI/CD

For general system setup, see page 2 (Getting Started). For component details, see page 3 (Components).

Sources: .github/workflows/ci.yml, .github/workflows/build-docs.yml, .gitignore


Automation Pipeline Overview

The system uses GitHub Actions to automate quality assurance and documentation maintenance. Both pipelines execute in isolated GitHub-hosted runners and operate independently.

Pipeline Comparison

AspectCI PipelineDocumentation Pipeline
Workflow File.github/workflows/ci.yml.github/workflows/build-docs.yml
TriggerPush or PR to mainWeekly schedule + manual dispatch
Runnerubuntu-latestubuntu-latest
Primary ActionTest service startupGenerate and deploy docs
Duration~2 minutes~5 minutes
External DependenciesDocker ComposeDeepWiki, GitHub Pages
Failure ImpactBlocks mergeDoes not block development

Sources: .github/workflows/ci.yml, .github/workflows/build-docs.yml

Automation Architecture

Diagram: GitHub Actions Pipeline Architecture

This diagram illustrates the two independent automation pipelines. The CI pipeline .github/workflows/ci.yml:1-43 executes on code changes, while the documentation pipeline .github/workflows/build-docs.yml:1-81 runs on a weekly schedule. Both use GitHub-hosted runners but have different trigger mechanisms and purposes.

Sources: .github/workflows/ci.yml, .github/workflows/build-docs.yml


5.1 Continuous Integration

The CI pipeline validates that the Mosquitto MQTT broker can successfully start in a Docker environment. This automated test runs on every push to main and on all pull requests targeting main.

Workflow Configuration

The workflow is defined in .github/workflows/ci.yml:1-43 with the following key elements:

Trigger Configuration .github/workflows/ci.yml:3-9:

Job Definition .github/workflows/ci.yml:11-13:

  • Job Name: test-mosquitto
  • Runner: ubuntu-latest
  • Purpose: Validate Mosquitto container can start and reach running state

CI Execution Steps

StepActionPurposeExit Condition
1. Checkoutactions/checkout@v2Clone repositoryAlways succeeds
2. Setupapt-get install docker-composeInstall Docker ComposePackage installation complete
3. Start Servicedocker-compose up -d mosquittoLaunch Mosquitto containerContainer created
4. Health CheckPoll container statusVerify running stateRunning or timeout (100s)
5. Teardowndocker-compose downClean up resourcesAlways executes

Health Check Mechanism

Diagram: Health Check Loop Logic

The health check loop .github/workflows/ci.yml:28-39 implements a polling strategy with bounded retry:

This bash loop:

  • Executes up to 10 iterations
  • Uses docker inspect to query container state
  • Checks if status contains “running”
  • Waits 10 seconds between attempts
  • Fails after 100 seconds total (10 × 10s)

Sources: .github/workflows/ci.yml:28-39

Why Only Mosquitto Is Tested

The CI pipeline starts only the mosquitto service, not the cloudflared service .github/workflows/ci.yml25:

Rationale:

  • The cloudflared container requires CLOUDFLARE_TUNNEL_TOKEN environment variable
  • This token is a secret that cannot be exposed in public CI logs
  • The mosquitto service has no external dependencies and can run in isolation
  • Starting Mosquitto validates the core MQTT broker functionality

The docker-compose.yml file docker-compose.yml:4-9 defines mosquitto as an independent service that doesn’t depend on cloudflared, enabling this isolated testing approach.

Sources: .github/workflows/ci.yml:25, docker-compose.yml:4-9

Failure Scenarios

Failure TypeSymptomLikely CauseCI Outcome
Container won’t startExits before 100sInvalid mosquitto.conf syntaxExit 1, workflow fails
Container starts then crashesStatus changes from runningConfiguration error (port conflict, permissions)Exit 1, workflow fails
TimeoutNever reaches running stateResource constraints, slow pullExit 1, workflow fails
Teardown failuredocker-compose down failsDocker daemon issueStep fails, workflow fails

When the CI workflow fails, it blocks pull request merges, preventing broken configurations from entering the main branch.

Sources: .github/workflows/ci.yml:28-42


5.2 Documentation Pipeline

The documentation pipeline automatically generates and deploys technical documentation to GitHub Pages. It uses the DeepWiki documentation generation system to create an mdBook-formatted static site from the repository’s codebase.

Workflow Configuration

The workflow is defined in .github/workflows/build-docs.yml:1-81 with distinct build and deploy jobs.

Trigger Configuration .github/workflows/build-docs.yml:3-7:

The pipeline executes in two scenarios:

  1. Scheduled: Every Sunday at 00:00 UTC via cron schedule
  2. Manual: Triggered through GitHub UI via workflow_dispatch

Permissions .github/workflows/build-docs.yml:9-12:

These permissions enable:

  • Reading repository contents
  • Writing to GitHub Pages deployment
  • Generating OIDC tokens for secure deployment

Build Job

The build job .github/workflows/build-docs.yml:19-69 performs documentation generation:

Diagram: Documentation Build Job Flow

Step 1: Repository Resolution

.github/workflows/build-docs.yml:25-52 determines which repository to document:

This logic allows manual workflow dispatch to specify a different repository, defaulting to the current repository (github.repository context variable).

Step 2: Title Generation

.github/workflows/build-docs.yml:37-47 generates the documentation book title:

For this repository, the generated title would be: "docker-mqtt-mosquitto-cloudflare-tunnel Documentation"

Step 3: DeepWiki Generation

.github/workflows/build-docs.yml:59-64 invokes the DeepWiki documentation generator:

The jzombie/deepwiki-to-mdbook action:

  • Analyzes the repository structure
  • Generates markdown documentation pages
  • Creates an mdBook configuration
  • Outputs static HTML to ./output/book

Step 4: Artifact Upload

.github/workflows/build-docs.yml:66-69 uploads the generated documentation:

This creates a GitHub Actions artifact containing the compiled mdBook site, which is consumed by the deploy job.

Sources: .github/workflows/build-docs.yml:19-69

Deploy Job

The deploy job .github/workflows/build-docs.yml:71-81 publishes documentation to GitHub Pages:

Diagram: Documentation Deploy Job Flow

The deploy job configuration .github/workflows/build-docs.yml:71-80:

Key aspects:

  • Dependency: needs: build ensures build completes first
  • Environment: Publishes to github-pages environment
  • Output: page_url provides the live documentation URL
  • Idempotency: Overwrites existing GitHub Pages deployment

Sources: .github/workflows/build-docs.yml:71-81

Concurrency Control

.github/workflows/build-docs.yml:14-16 prevents concurrent documentation builds:

This configuration:

  • Groups all Pages deployments under single concurrency group
  • Queues new runs instead of canceling in-progress deployments
  • Prevents race conditions in GitHub Pages publishing

Sources: .github/workflows/build-docs.yml:14-16

Documentation Update Frequency

The weekly schedule ensures documentation stays current without excessive CI usage:

Update TriggerFrequencyPurpose
ScheduledWeekly (Sunday 00:00 UTC)Regular refresh
ManualOn-demand via UIImmediate update after major changes
AutomaticNever on pushPrevents CI overload

Manual triggering is accessible via: Actions → Build and Deploy Documentation → Run workflow

Sources: .github/workflows/build-docs.yml:3-7


5.3 Version Control Best Practices

The repository implements strict separation between public configuration and sensitive secrets to prevent credential leakage in version control.

Gitignore Configuration

The .gitignore file .gitignore:1-2 enforces this separation:

.env
data/*

Rationale:

Excluded PathTypeReasonAlternative
.envSecret fileContains CLOUDFLARE_TUNNEL_TOKEN.env.sample provides template
data/*Runtime directoryMosquitto persistent storageCreated automatically by container

Secret Management Pattern

Diagram: Secret Management Flow in Version Control

This diagram shows how secrets flow from Cloudflare Dashboard to Docker containers without entering version control. The .gitignore file .gitignore1 acts as a security boundary.

Sources: .gitignore:1-2

Tracked vs Untracked Files

File PathVersion ControlledContains SecretsPurpose
.env.sample✓ Yes✗ NoDocuments required variables
.env✗ No✓ YesStores CLOUDFLARE_TUNNEL_TOKEN
docker-compose.yml✓ Yes✗ NoReferences ${CLOUDFLARE_TUNNEL_TOKEN} variable
mosquitto.conf✓ Yes✗ NoPublic broker configuration
.github/workflows/ci.yml✓ Yes✗ NoCI pipeline definition
.github/workflows/build-docs.yml✓ Yes✗ NoDocumentation pipeline
data/*✗ No⚠ MaybeRuntime MQTT persistence (may contain messages)

Git Workflow Integration

The CI pipeline .github/workflows/ci.yml:3-9 enforces version control best practices:

Protected Branch Pattern:

  • All changes to main require pull request
  • CI must pass before merge
  • Prevents broken configuration from entering main branch

Pull Request Flow:

  1. Developer creates feature branch
  2. Pushes changes to remote
  3. Opens pull request to main
  4. CI workflow executes automatically .github/workflows/ci.yml:7-9
  5. If CI passes, pull request can be merged
  6. If CI fails, developer must fix issues

Environment Variable Security

The docker-compose.yml file docker-compose.yml:16-17 references the token without exposing it:

This pattern:

  • Uses variable substitution instead of hardcoded values
  • Reads from .env file at runtime (excluded by .gitignore)
  • Never logs the token value in CI output
  • Follows twelve-factor app methodology

Sources: docker-compose.yml:16-17, .gitignore:1

Data Directory Management

The data/* directory .gitignore2 is excluded for several reasons:

  1. Size: Mosquitto may accumulate significant message data
  2. Privacy: MQTT messages might contain sensitive IoT data
  3. State: Runtime state should not be in version control
  4. Portability: Each deployment should have independent state

The directory is automatically created by the Mosquitto container when it starts.

Sources: .gitignore:2

Commit Hygiene for CI/CD

DO:

  • Commit workflow YAML files .github/workflows/
  • Commit public configuration mosquitto.conf
  • Update .env.sample when adding new required variables
  • Include descriptive commit messages for CI changes

DON’T:

  • Commit .env files with actual tokens
  • Commit data/* runtime directories
  • Hardcode secrets in workflow files
  • Bypass CI by pushing directly to main

Security Checklist

Before committing changes related to CI/CD:

  • Verify .gitignore excludes .env
  • Confirm workflow files contain no hardcoded secrets
  • Check that .env.sample is up-to-date
  • Ensure CI workflow would catch your changes
  • Review git status for untracked secret files
  • Use git diff --cached to review staged changes

Sources: .gitignore:1-2, .github/workflows/ci.yml, .github/workflows/build-docs.yml


Testing Strategy

The automated testing focuses on validating successful service startup rather than functional MQTT testing:

Test AspectImplementationLocation
TriggerPush or PR to main branch.github/workflows/ci.yml:3-9
Environmentubuntu-latest runner.github/workflows/ci.yml13
Service Under Testmosquitto only.github/workflows/ci.yml25
Health CheckContainer status polling.github/workflows/ci.yml:28-39
Timeout100 seconds (10 × 10s).github/workflows/ci.yml29
CleanupAlways runs teardown.github/workflows/ci.yml:41-42

Note that the cloudflared service is not tested in CI because it requires a valid CLOUDFLARE_TUNNEL_TOKEN, which is not available in the test environment. The CI validates only that the mosquitto service can successfully start in isolation.

Sources: .github/workflows/ci.yml


Development Commands

Developers use the following docker-compose commands during development:

CommandPurposeEffect
docker-compose up -dStart all services in backgroundStarts both mosquitto and cloudflared
docker-compose up -d mosquittoStart only mosquittoStarts mosquitto without cloudflared
docker-compose psCheck service statusShows running containers and their state
docker-compose logs -f mosquittoView mosquitto logsStreams log output in real-time
docker-compose downStop and remove servicesStops containers and removes them
docker-compose restart mosquittoRestart mosquittoStops and starts the service

These commands interact with the service definitions in docker-compose.yml:3-17

Sources: docker-compose.yml, .github/workflows/ci.yml


Configuration Management

Environment Variables

The system uses environment variable substitution in docker-compose.yml docker-compose.yml:14-17:

The CLOUDFLARE_TUNNEL_TOKEN variable must be defined in the .env file for the cloudflared service to authenticate with Cloudflare’s network.

Volume Mounts

The mosquitto configuration is provided via volume mount docker-compose.yml:7-8:

This allows developers to modify mosquitto.conf and restart the service without rebuilding any Docker images.

Sources: docker-compose.yml


Next Steps for Developers

After understanding this overview:

  1. Setup Local Environment: Follow the detailed instructions in Local Development Setup to configure your workstation
  2. Understand CI Pipeline: Review CI/CD Pipeline for details on how automated tests validate changes
  3. Learn Version Control: Read Version Control Practices for guidelines on managing secrets and contributions

For advanced configuration topics such as ACLs and encryption, see Advanced Topics.

Sources: All files in this repository

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Continuous Integration

Loading…

Continuous Integration

Relevant source files

Purpose and Scope

This page documents the automated testing workflow defined in .github/workflows/ci.yml:1-43 that validates the mosquitto service can be successfully deployed. The workflow triggers on push and pull request events to the main branch and verifies the Mosquitto MQTT broker container starts and reaches a running state.

Scope : Tests the mosquitto service only. The cloudflared service is excluded because it requires CLOUDFLARE_TUNNEL_TOKEN which cannot be securely provided in CI environments.

Workflow Overview

The CI workflow (.github/workflows/ci.yml1) defines a single job test-mosquitto (.github/workflows/ci.yml12) that runs on ubuntu-latest (.github/workflows/ci.yml13):

AttributeValue
Workflow NameCI
Job Nametest-mosquitto
Runnerubuntu-latest
Service Testedmosquitto from docker-compose.yml:4-9
Triggerspush to main, pull_request to main
ValidationContainer state inspection with 10-attempt retry loop
Timeout100 seconds (10 × 10s)

Sources: .github/workflows/ci.yml:1-43 docker-compose.yml:4-9

Workflow Configuration

Trigger Configuration

The workflow executes on two GitHub event types:

Sources: .github/workflows/ci.yml:3-9

Job Architecture

test-mosquitto Job Flow

Sources: .github/workflows/ci.yml:1-43 docker-compose.yml:4-9

Execution Sequence

Sources: .github/workflows/ci.yml:1-43

Workflow Steps

Step 1: Checkout repository

Uses actions/checkout@v2 (.github/workflows/ci.yml:16-17) to clone the repository, making docker-compose.yml:1-18 and mosquitto.conf:1-5 available to subsequent steps.

Sources: .github/workflows/ci.yml:16-17

Step 2: Set up Docker Compose

Installs docker-compose via apt-get (.github/workflows/ci.yml:19-22). Docker Engine is pre-installed on ubuntu-latest, but Docker Compose requires explicit installation.

Sources: .github/workflows/ci.yml:19-22

Step 3: Start Mosquitto service using Docker Compose

Executes docker-compose up -d mosquitto (.github/workflows/ci.yml:24-25), starting only the mosquitto service from docker-compose.yml:4-9 The -d flag runs the container in detached mode. The cloudflared service (docker-compose.yml:11-17) is excluded because it requires CLOUDFLARE_TUNNEL_TOKEN.

Sources: .github/workflows/ci.yml:24-25 docker-compose.yml:4-9

Step 4: Wait for Mosquitto to be healthy

Implements a bounded retry loop (.github/workflows/ci.yml:27-39):

ParameterValue
Max Attempts10
Sleep Interval10 seconds
Total Timeout100 seconds
Inspection Commanddocker inspect --format='{{.State.Status}}' mosquitto
Success ConditionStatus equals running

Health Check Logic Flow

Sources: .github/workflows/ci.yml:27-39

Step 5: Stop Mosquitto service using Docker Compose

Executes docker-compose down (.github/workflows/ci.yml:41-42) to stop and remove the mosquitto container. This step always runs regardless of health check outcome.

Sources: .github/workflows/ci.yml:41-42

Test Coverage

The workflow validates:

AspectValidation Mechanism
eclipse-mosquitto:latest image availabilitydocker-compose up pulls image from Docker Hub
docker-compose.yml:4-9 service definitionContainer instantiation succeeds
mosquitto.conf:1-5 syntax validityContainer starts without error
Volume mount ./mosquitto.conf:/mosquitto/config/mosquitto.confImplicit validation during startup
Mosquitto broker process initializationContainer reaches running state

Sources: .github/workflows/ci.yml:1-43 docker-compose.yml:4-9 mosquitto.conf:1-5

Excluded from Testing

cloudflared Service

The cloudflared service (docker-compose.yml:11-17) is not tested because:

  1. Requires CLOUDFLARE_TUNNEL_TOKEN (docker-compose.yml17)
  2. Token is environment-specific and authenticates with Cloudflare’s network
  3. Exposing a real token in CI logs would create a security vulnerability

The workflow explicitly specifies docker-compose up -d mosquitto (.github/workflows/ci.yml25), omitting the cloudflared service.

Sources: .github/workflows/ci.yml:24-25 docker-compose.yml:11-17

Protocol and Functional Testing

Not tested:

Excluded TestReason
MQTT protocol connectivity (ports 1883, 9001)No MQTT client invoked
Message publish/subscribe operationsRequires mosquitto_pub/mosquitto_sub clients
WebSocket protocolNo WebSocket connection attempted
Authentication/authorizationConfiguration uses allow_anonymous true
Performance/load testingOutside scope of health check

Sources: .github/workflows/ci.yml:1-43 mosquitto.conf:1-5

Success and Failure Conditions

Success

The workflow exits with code 0 (.github/workflows/ci.yml32) when:

Returns true within 10 attempts, indicating:

  1. Container started with configuration from mosquitto.conf:1-5
  2. Volume mount ./mosquitto.conf:/mosquitto/config/mosquitto.conf loaded successfully
  3. Mosquitto broker process initialized
  4. Container state is running

Sources: .github/workflows/ci.yml:29-32 mosquitto.conf:1-5 docker-compose.yml8

Failure

Health Check Timeout

Exits with code 1 (.github/workflows/ci.yml39) after 10 failed attempts:

Waiting for Mosquitto to be healthy...
(10 iterations)
Mosquitto did not become healthy in time

Potential causes:

  • Invalid mosquitto.conf:1-5 syntax
  • Missing ./mosquitto.conf file
  • Resource constraints on runner
  • Docker Hub rate limiting

Sources: .github/workflows/ci.yml:38-39

Docker Compose Failures

Failures during docker-compose up -d mosquitto (.github/workflows/ci.yml25):

FailureCause
Image pull errorDocker Hub connectivity issue
YAML parse errorInvalid docker-compose.yml:1-18 syntax
Volume mount error./mosquitto.conf not found

Sources: .github/workflows/ci.yml:24-25 docker-compose.yml:1-18

Local Test Replication

Replicate the CI workflow locally:

Sources: .github/workflows/ci.yml:24-42

Debugging Workflow Failures

Failure Diagnosis

Sources: .github/workflows/ci.yml:1-43

Adding Container Log Output

To capture container logs on failure, add this step after .github/workflows/ci.yml39:

Sources: .github/workflows/ci.yml:27-42

Limitations

Test Scope

LimitationImpact
No cloudflared testingTunnel connectivity not validated
No protocol testingMQTT/WebSocket functionality not verified
No persistence testingData retention not checked (no volumes configured)
No security testingAnonymous access not explicitly validated

Sources: .github/workflows/ci.yml:1-43 mosquitto.conf:1-5

Runner Environment

ubuntu-latest (.github/workflows/ci.yml13) may differ from production:

  • Architecture: typically x86_64
  • Network: isolated runner environment
  • Resources: GitHub-managed limits
  • Docker version: may lag behind latest releases

Sources: .github/workflows/ci.yml13

Timeout Consideration

The 100-second timeout (.github/workflows/ci.yml:29-39) accommodates:

  • Docker Hub rate limiting
  • Runner load variations
  • Network latency

Typical startup time: 5-10 seconds in production.

Sources: .github/workflows/ci.yml:27-39

Potential Enhancements

Extended Coverage

Possible additions to .github/workflows/ci.yml:1-43:

EnhancementImplementation
MQTT protocol testAdd mosquitto_pub/mosquitto_sub client steps
WebSocket testAdd MQTT.js connection step
Mock cloudflaredStub tunnel with local proxy
Config validationRun mosquitto -t -c mosquitto.conf
ACL testingTest access control (requires password file)

Sources: .github/workflows/ci.yml:1-43

Summary

The CI/CD pipeline provides basic validation that the Mosquitto MQTT broker can be successfully deployed using the repository’s Docker Compose configuration. The pipeline:

  • Executes on every push and pull request to main
  • Tests only the mosquitto service (excludes cloudflared)
  • Validates container startup and health within 100 seconds
  • Ensures configuration files are syntactically valid
  • Provides immediate feedback to developers on infrastructure changes

While limited in scope, this pipeline prevents basic configuration errors from reaching production and provides a foundation for more comprehensive testing in the future.

Sources: .github/workflows/ci.yml:1-43 docker-compose.yml:1-18 mosquitto.conf:1-7

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Documentation Pipeline

Loading…

Documentation Pipeline

Relevant source files

Purpose and Scope

This document describes the automated documentation generation and deployment system that converts repository content into a browsable static site hosted on GitHub Pages. The pipeline uses the DeepWiki documentation generation tool to analyze the codebase and produce structured technical documentation.

For information about the continuous integration testing pipeline, see Continuous Integration. For Git workflow and secret management practices, see Version Control Best Practices.


Overview

The documentation pipeline is implemented as a GitHub Actions workflow that automatically generates and deploys documentation to GitHub Pages. The workflow is defined in .github/workflows/build-docs.yml:1-81 and operates independently of the main CI pipeline. It uses the deepwiki-to-mdbook action to analyze the repository structure and generate mdBook-formatted documentation.

Key Characteristics

PropertyValue
Workflow File.github/workflows/build-docs.yml
Trigger ScheduleWeekly (Sundays at midnight UTC)
Manual TriggerAvailable via workflow_dispatch
Documentation Tooldeepwiki-to-mdbook
Output FormatmdBook static site
Deployment TargetGitHub Pages
Permissions Requiredcontents: read, pages: write, id-token: write

Sources: .github/workflows/build-docs.yml:1-12


Workflow Triggers

The pipeline supports two activation mechanisms configured in the on section of the workflow:

Scheduled Execution

The workflow executes automatically every Sunday at midnight UTC. This weekly cadence ensures documentation stays reasonably current without excessive build frequency. The cron expression "0 0 * * 0" specifies minute 0, hour 0, any day of month, any month, day 0 (Sunday).

Manual Dispatch

The workflow_dispatch trigger enables manual workflow execution through the GitHub Actions UI. This allows documentation regeneration on-demand when significant repository changes occur or when immediate documentation updates are required.

Sources: .github/workflows/build-docs.yml:3-7


Permissions and Concurrency

Permission Model

The workflow requires three specific permissions defined at the workflow level:

PermissionScopePurpose
contents: readRepository contentAccess source files for documentation generation
pages: writeGitHub PagesDeploy generated documentation artifacts
id-token: writeOIDC tokenAuthenticate with GitHub Pages deployment service

Concurrency Control

The pages concurrency group ensures only one documentation deployment runs at a time. The cancel-in-progress: false setting prevents cancellation of in-flight deployments when new builds are triggered, ensuring documentation builds complete even if overlapping triggers occur.

Sources: .github/workflows/build-docs.yml:9-16


Build Job Architecture

The build job executes on ubuntu-latest and consists of five sequential steps that prepare, generate, and package the documentation.

flowchart TD
    Trigger["Trigger Event\n(schedule or workflow_dispatch)"]
subgraph BuildJob["build job (ubuntu-latest)"]
CheckoutStep["actions/checkout@v4\nClone repository"]
ResolveStep["Resolve repository and book title\nShell script logic"]
PrepareStep["Prepare output directory\nrm -rf output && mkdir"]
GenerateStep["jzombie/deepwiki-to-mdbook@main\nGenerate documentation"]
UploadStep["actions/upload-pages-artifact@v3\nPackage for Pages"]
end
    
    subgraph DeployJob["deploy job (ubuntu-latest)"]
DeployStep["actions/deploy-pages@v4\nPublish to GitHub Pages"]
end
    
 
   Trigger --> CheckoutStep
 
   CheckoutStep --> ResolveStep
 
   ResolveStep --> PrepareStep
 
   PrepareStep --> GenerateStep
 
   GenerateStep --> UploadStep
 
   UploadStep --> DeployStep
    
 
   ResolveStep -.->|repo_value title_value| GenerateStep

Workflow Execution Diagram

Sources: .github/workflows/build-docs.yml:18-69


Repository and Title Resolution

The “Resolve repository and book title” step implements dynamic configuration logic that determines which repository to document and what title to use.

Resolution Logic

The step defined in .github/workflows/build-docs.yml:25-52 executes a shell script with the following logic:

Output Variables

The resolution step produces two output variables accessible to subsequent steps:

VariableSource PriorityExample Value
repo_value1. Manual input
2. Current repositoryjzombie/docker-mqtt-mosquitto-cloudflare-tunnel
title_value1. Manual input
2. Derived from repo name
3. Default “Documentation”docker-mqtt-mosquitto-cloudflare-tunnel Documentation

Sources: .github/workflows/build-docs.yml:25-52


Documentation Generation

The core documentation generation step uses the jzombie/deepwiki-to-mdbook@main action to analyze the repository and produce mdBook output.

Generation Step Configuration

Input Parameters

ParameterValue SourcePurpose
reposteps.resolve.outputs.repo_valueRepository identifier for DeepWiki analysis
book_titlesteps.resolve.outputs.title_valueTitle displayed in generated documentation
output_dir./outputDirectory where mdBook site is generated

Output Directory Structure

The generation step produces the following directory structure:

output/
└── book/
    ├── index.html
    ├── *.html (generated pages)
    └── (mdBook assets)

The output/book/ directory contains the complete static site ready for deployment.

Sources: .github/workflows/build-docs.yml:59-64


GitHub Pages Deployment

The deployment process uses GitHub’s standard Pages deployment actions to publish the generated documentation.

Artifact Upload

The actions/upload-pages-artifact@v3 action packages the ./output/book directory as a GitHub Actions artifact specifically formatted for Pages deployment. This artifact is stored temporarily and consumed by the deployment job.

Deployment Job

The deploy job:

  • Depends on successful completion of the build job via needs: build
  • Targets the github-pages environment
  • Exposes the deployment URL via steps.deployment.outputs.page_url
  • Uses actions/deploy-pages@v4 to publish the artifact

Sources: .github/workflows/build-docs.yml:66-80


Pipeline Data Flow

Sources: .github/workflows/build-docs.yml:1-81


Pipeline Behavior and Characteristics

Idempotency

The pipeline is fully idempotent. Multiple executions with the same repository state produce identical documentation output. The preparation step .github/workflows/build-docs.yml:54-57 explicitly removes and recreates the output directory to ensure clean builds.

Failure Modes

Failure PointEffectRecovery
Repository checkout failsBuild job fails, no deploymentRetry workflow
Resolution step failsBuild job fails, outputs undefinedCheck step logic
DeepWiki generation failsBuild job fails, no artifactReview repository structure
Artifact upload failsDeployment job cannot startRetry workflow
Pages deployment failsOld documentation remains liveCheck Pages configuration

Build Duration

Typical pipeline execution times:

  • Repository checkout: 5-10 seconds
  • Documentation generation: 2-5 minutes (varies with repository size)
  • Artifact upload: 10-30 seconds
  • Pages deployment: 30-60 seconds
  • Total pipeline duration: ~3-7 minutes

Sources: .github/workflows/build-docs.yml:18-80


Integration with Repository

The documentation pipeline operates independently but maintains integration with the repository through several mechanisms:

Repository Settings Requirements

For the pipeline to function, the repository must have GitHub Pages enabled:

  1. Navigate to SettingsPages
  2. Set Source to “GitHub Actions”
  3. No branch selection required (Actions-based deployment)

Permission Configuration

The workflow uses the default GITHUB_TOKEN with elevated permissions defined in .github/workflows/build-docs.yml:9-12 No additional secrets or tokens are required.

Workflow Dependencies

Sources: .github/workflows/build-docs.yml:9-16


Customization and Extension

Modifying Schedule

To change the build frequency, edit the cron expression in .github/workflows/build-docs.yml:5-6:

Customizing Book Title

The book title can be overridden by:

  1. Manual workflow dispatch with custom book_title input
  2. Modifying the derived title logic in .github/workflows/build-docs.yml:41-46

Output Directory

The output directory is hardcoded as ./output in multiple steps:

Changing the directory requires updating all three references.

Sources: .github/workflows/build-docs.yml:4-69


Monitoring and Verification

Workflow Execution Status

View workflow runs at: https://github.com/<owner>/<repo>/actions/workflows/build-docs.yml

Each run shows:

  • Trigger type (scheduled or manual)
  • Execution time
  • Build job status
  • Deploy job status
  • Deployed Pages URL

Deployment Verification

After successful deployment:

  1. Navigate to repository SettingsPages
  2. Verify the displayed URL matches expected documentation site
  3. Access the URL to confirm documentation content

Troubleshooting Failed Builds

Check the workflow logs for common issues:

  • Repository checkout fails : Verify repository permissions
  • DeepWiki generation fails : Check repository structure and content
  • Artifact upload fails : Verify GitHub Actions storage limits
  • Pages deployment fails : Confirm Pages is enabled and configured for Actions

Sources: .github/workflows/build-docs.yml:1-81

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Version Control Best Practices

Loading…

Version Control Best Practices

Relevant source files

Purpose and Scope

This document explains the version control practices and configurations implemented in this repository to ensure secure handling of secrets and maintain a clean Git history. It focuses on the .gitignore configuration, the template-based approach for sensitive configuration, and recommended workflows for committing changes safely.

For information about the automated CI/CD workflows themselves, see Continuous Integration and Documentation Pipeline. For general configuration management, see Configuration Reference.


File Classification Strategy

The repository implements a strict separation between code/documentation (version controlled) and secrets/runtime data (explicitly excluded). This classification prevents accidental exposure of sensitive information.

graph TB
    subgraph VersionControlled["Version Controlled (Committed to Git)"]
DockerCompose["docker-compose.yml\nService orchestration"]
MosquittoConf["mosquitto.conf\nBroker configuration"]
EnvSample[".env.sample\nSecret template"]
Workflows["GitHub Actions workflows\n.github/workflows/*"]
README["README.md\nDocumentation"]
Gitignore[".gitignore\nExclusion rules"]
end
    
    subgraph Ignored["Explicitly Ignored (Never Committed)"]
EnvFile[".env\nContains CLOUDFLARE_TUNNEL_TOKEN"]
DataDir["data/*\nRuntime broker data"]
end
    
    subgraph Runtime["Runtime Environment"]
DockerEngine["Docker Engine"]
CloudflaredContainer["cloudflared container"]
MosquittoContainer["mosquitto container"]
end
    
 
   DockerCompose -->|orchestrates| DockerEngine
 
   MosquittoConf -->|mounted into| MosquittoContainer
 
   EnvSample -->|developer copies to| EnvFile
 
   Gitignore -->|excludes| EnvFile
 
   Gitignore -->|excludes| DataDir
 
   EnvFile -->|loaded by docker-compose| CloudflaredContainer
 
   DataDir -->|mounted into| MosquittoContainer

File Classification Diagram

Sources : .gitignore:1-2 .env.sample1 docker-compose.yml


The .gitignore Configuration

The .gitignore file serves as the primary security boundary, preventing sensitive files from being committed to version control.

Current Exclusions

PatternPurposeRisk if Committed
.envEnvironment variables containing CLOUDFLARE_TUNNEL_TOKENExposes tunnel access credentials, allowing unauthorized traffic routing
data/*Mosquitto runtime data (messages, persistence files)May contain sensitive message content, creates unnecessary repository bloat

Sources : .gitignore:1-2

Security Implications

The .env file exclusion is critical because it contains the CLOUDFLARE_TUNNEL_TOKEN value. This token provides authentication to route traffic through your Cloudflare Tunnel. If committed to a public repository (or even a private one with multiple collaborators), the token could be:

  1. Extracted from Git history - Even if deleted in a later commit, the token remains in Git history
  2. Used to route unauthorized traffic - An attacker with the token can send traffic through your tunnel
  3. Difficult to rotate - Revoking and regenerating the token requires reconfiguring the entire Cloudflare Tunnel

Sources : .env.sample1 .gitignore1


Template-Based Secret Management

The repository uses a template file pattern to document required secrets without exposing actual values.

sequenceDiagram
    participant Dev as "Developer"
    participant Repo as "Git Repository"
    participant Local as "Local Filesystem"
    participant Docker as "Docker Compose"
    participant CF as "Cloudflare API"
    
    Note over Repo: .env.sample checked into Git\nNote over Repo: .gitignore excludes .env
    
    Dev->>Repo: git clone repository
    Repo->>Local: .env.sample available
    Repo->>Local: .gitignore rules applied
    Dev->>CF: Create tunnel via dashboard
    CF-->>Dev: Receive CLOUDFLARE_TUNNEL_TOKEN
    Dev->>Local: cp .env.sample .env
    Dev->>Local: Edit .env with actual token
    Note over Local: .env now contains secret
    Dev->>Docker: docker-compose up
    Docker->>Local: Read .env file
    Local-->>Docker: CLOUDFLARE_TUNNEL_TOKEN value
    Docker->>Docker: Inject into cloudflared container
    
    Note over Dev,Local: .env never committed due to .gitignore

Template File Flow

Sources : .env.sample1 .gitignore1

The .env.sample Template

The .env.sample file serves three purposes:

  1. Documentation - Shows developers what environment variables are required
  2. Template - Provides copy-paste structure for creating the actual .env file
  3. Placeholder Values - Uses obvious placeholder text (your_token) that will fail if not replaced

Content:

CLOUDFLARE_TUNNEL_TOKEN=your_token

This template is intentionally minimal. The placeholder value your_token will cause the tunnel connection to fail, forcing developers to replace it with a real token from the Cloudflare dashboard.

Sources : .env.sample1


Protected Files and Their Purposes

Files That Are Safe to Commit

FileContains Secrets?Why It’s Safe
docker-compose.ymlNoReferences environment variable by name (${CLOUDFLARE_TUNNEL_TOKEN}), not value
mosquitto.confNoOnly contains port numbers and protocol settings
.env.sampleNoContains placeholder text, not real secrets
README.mdNoDocumentation only

Sources : .env.sample1 docker-compose.yml mosquitto.conf

Files That Must Never Be Committed

File/DirectoryContains Secrets?Contents
.envYESReal CLOUDFLARE_TUNNEL_TOKEN value
data/*PotentiallyMQTT message persistence, retained messages, subscription state

Sources : .gitignore:1-2


Git Workflow Best Practices

Pre-Commit Verification Process

Sources : .gitignore:1-2

  1. Initial Setup (One-time per developer)

    • Clone repository: git clone <repository-url>
    • Verify .gitignore exists: cat .gitignore
    • Copy template: cp .env.sample .env
    • Populate .env with real token from Cloudflare dashboard
    • Verify exclusion: git status should not show .env
  2. Before Each Commit

    • Review staged files: git status
    • Confirm .env is NOT in the staged files list
    • Confirm data/* is NOT in the staged files list
    • If either appears, investigate why (usually means .gitignore is missing or misconfigured)
  3. After Cloning on a New Machine

    • The .env file will not exist (this is correct)
    • Must manually create .env from .env.sample template
    • Must obtain new token from Cloudflare or securely transfer existing token

Sources : .gitignore:1-2 .env.sample1


Verification Commands

Checking Gitignore Effectiveness

Sources : .gitignore1

Staged Files Safety Check

Before committing, run this command to list all files that will be committed:

This output should never include:

  • .env
  • Any file under data/

If either appears, do not commit. Instead:

Sources : .gitignore:1-2


sequenceDiagram
    participant Admin as "Administrator"
    participant CF as "Cloudflare Dashboard"
    participant Local as "Local .env File"
    participant Docker as "Docker Environment"
    participant Git as "Git Repository"
    
    Note over Admin,Git: Token Rotation Process
    Admin->>CF: Regenerate tunnel token
    CF-->>Admin: New CLOUDFLARE_TUNNEL_TOKEN
    Admin->>Local: Update .env with new token
    Note over Local: Old token still in file
    Admin->>Docker: docker-compose down
    Admin->>Docker: docker-compose up
    Docker->>Local: Read updated .env
    Note over Docker: New token now active
    
    Note over Admin,Git: Verification
    Admin->>Git: git status
    Git-->>Admin: .env not in changes
    Note over Admin: Confirm .gitignore still protecting .env
    
    Admin->>CF: Verify tunnel connected
    CF-->>Admin: Tunnel active with new token

Secret Rotation Procedure

When the CLOUDFLARE_TUNNEL_TOKEN needs to be rotated (compromised token, security policy, or regular rotation):

Rotation Flow Diagram

Sources : .env.sample1 .gitignore1

Step-by-Step Rotation

  1. Generate new token in Cloudflare Dashboard (Networks → Tunnels → Select tunnel → Configure)
  2. Update local.env file with new CLOUDFLARE_TUNNEL_TOKEN value
  3. Restart services : docker-compose down && docker-compose up -d
  4. Verify exclusion : Run git status to confirm .env is not staged
  5. Test connection : Verify tunnel connects with new token
  6. Revoke old token (optional, if Cloudflare UI supports it)

Important : The .env file is never committed during this process. The token change remains local to each deployment environment.

Sources : .env.sample1 .gitignore1


Common Mistakes to Avoid

Mistake 1: Committing .env Before Adding .gitignore

Scenario : Developer commits .env file before creating .gitignore.

Result : Token is now in Git history permanently.

Prevention :

  • Ensure .gitignore exists before first commit
  • This repository includes .gitignore from initial commit

Recovery (if it happens):

Sources : .gitignore1

Mistake 2: Using .env.sample Directly

Scenario : Developer renames .env.sample to .env instead of copying.

Result : The template file disappears from version control.

Prevention :

  • Always use cp .env.sample .env (copy, not move)
  • Never run git rm .env.sample

Sources : .env.sample1

Mistake 3: Accidentally Staging with git add -A

Scenario : Running git add -A or git add . without reviewing files.

Result : Might stage .env if .gitignore is misconfigured or missing.

Prevention :

  • Always run git status before git add
  • Use git add <specific-files> for granular control
  • Review git diff --cached before committing

Sources : .gitignore1


Integration with CI/CD

The version control practices integrate with the automated CI/CD pipelines:

CI Pipeline Behavior

The GitHub Actions CI workflow (.github/workflows/ci.yml) does not require the .env file because:

  1. It only tests the mosquitto service, not cloudflared
  2. The Mosquitto broker does not require the CLOUDFLARE_TUNNEL_TOKEN
  3. Secrets would be injected via GitHub Secrets for full-stack testing (not implemented in this repository)

Sources : .github/workflows/ci.yml docker-compose.yml

Documentation Pipeline

The documentation build workflow (.github/workflows/build-docs.yml) operates on the committed files only:

  • Reads .env.sample (committed) to document the template format
  • Never accesses .env (ignored) as it’s not present in the repository
  • Builds static documentation without requiring runtime secrets

Sources : .github/workflows/build-docs.yml .env.sample1


Production Environment Considerations

For production deployments, additional secret management practices should be considered:

Production Secret Management Table

EnvironmentSecret StorageInjection MethodRotation Strategy
Local Development.env filedocker-compose reads fileManual edit + restart
CI/CDGitHub SecretsEnvironment variables in workflowUpdate in GitHub UI
ProductionSecrets manager (AWS Secrets Manager, HashiCorp Vault, etc.)Environment variables via orchestrationAutomated rotation with zero-downtime

For production deployments beyond local development, consider:

  1. Secrets Manager Integration - Replace .env file with calls to a secrets management service
  2. Automated Rotation - Implement automatic token rotation with zero-downtime restarts
  3. Audit Logging - Track when secrets are accessed and by whom
  4. Encryption at Rest - Ensure secrets are encrypted when stored on disk

Sources : .env.sample1


Summary of Key Practices

  1. Never commit.env - The .gitignore configuration prevents this
  2. Always use.env.sample as template - Copy, don’t move
  3. Verify before each commit - Run git status and check for excluded files
  4. Rotate tokens out-of-band - Token updates never touch Git
  5. Audit Git history - Ensure secrets never entered version control

These practices implement the principle of “separation of code and configuration,” where sensitive credentials are injected at runtime rather than embedded in version-controlled artifacts.

Sources : .gitignore:1-2 .env.sample1

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Advanced Topics

Loading…

Advanced Topics

Relevant source files

This page covers advanced configuration options and production-ready features available in the protected-no-wildcard branch of the repository. While the main branch provides a straightforward MQTT broker setup with anonymous access and no encryption, the advanced branch introduces ACL-based topic restrictions, encrypted retained message storage, and additional safeguards for multi-tenant environments.

For step-by-step deployment instructions, see Getting Started. For basic component documentation, see Components. This page focuses on enhanced security features and production considerations not present in the base configuration.

Sources : README.md:5-11

Advanced Branch Overview

The repository maintains two primary branches with different security and feature profiles:

Featuremain Branchprotected-no-wildcard Branch
Anonymous AccessEnabled (mosquitto.conf2)User authentication required
Topic Access ControlNoneACL file with user-based restrictions
Wildcard Topic QueriesUnrestrictedRestricted to user’s own topics
Retained Message StoragePlaintextEncrypted with gocryptfs
Message PersistenceManual saveAuto-save after every message
Use CaseDevelopment, trusted environmentsProduction, multi-tenant systems

The protected-no-wildcard branch addresses security concerns in scenarios where:

  • Multiple users share a single MQTT broker
  • Topic privacy between users must be enforced
  • Retained messages contain sensitive data requiring encryption at rest
  • Filesystem-level message persistence protection is needed

Sources : README.md:5-11

Architecture Comparison

The following diagram illustrates the architectural differences between the two branches:

Diagram: Main vs Protected Branch Architecture

graph TB
    subgraph "main Branch Architecture"
        direction TB
        MQ_Main["mosquitto Container\n(main branch)"]
Conf_Main["mosquitto.conf\nallow_anonymous: true"]
Data_Main["data/\nPlaintext Storage"]
Conf_Main -->|Configures| MQ_Main
 
       MQ_Main -->|Writes Retained Messages| Data_Main
    end
    
    subgraph "protected-no-wildcard Branch Architecture"
        direction TB
        MQ_Protected["mosquitto Container\n(protected-no-wildcard)"]
Conf_Protected["mosquitto.conf\nallow_anonymous: false\nacl_file /mosquitto/config/aclfile"]
ACL["aclfile\nUser-based Topic Rules"]
Gocryptfs["gocryptfs Container\nEncryption Layer"]
Data_Encrypted["data-encrypted/\nEncrypted Filesystem"]
Data_Decrypted["data/\nDecrypted Mount Point"]
Conf_Protected -->|Configures| MQ_Protected
 
       ACL -->|Restricts Topics| MQ_Protected
 
       MQ_Protected -->|Writes to| Data_Decrypted
 
       Gocryptfs -->|Mounts| Data_Decrypted
 
       Gocryptfs -->|Encrypts to| Data_Encrypted
    end
    
    Client_Main["MQTT Client\n(Any)"]
Client_Protected["MQTT Client\n(Authenticated)"]
Client_Main -->|Anonymous Connect| MQ_Main
 
   Client_Protected -->|Username/Password| MQ_Protected

The protected branch introduces two key architectural components not present in the main branch:

  1. ACL File : Located at mosquitto/aclfile in the protected branch, this file defines user-specific topic access patterns
  2. Gocryptfs Layer : A separate container that provides transparent encryption for the data/ directory

Sources : README.md:5-11 mosquitto.conf:1-6

Topic Access Control (ACL)

The protected-no-wildcard branch implements an ACL (Access Control List) system that restricts MQTT topic access based on username. The implementation follows a naive but effective pattern where:

  • The first level of each topic must match the authenticated username
  • Users can only publish and subscribe to topics under their own namespace
  • Wildcard subscriptions (#, +) are restricted to prevent cross-user topic enumeration

Example ACL Pattern :

# User "alice" can only access topics starting with "alice/"
user alice
topic readwrite alice/#

# User "bob" can only access topics starting with "bob/"
user bob
topic readwrite bob/#

This approach prevents a scenario where user alice subscribes to bob/# or # to discover or intercept messages from other users.

For detailed ACL configuration and examples, see Topic Access Control (ACL).

Sources : README.md7

Encrypted Retained Messages

MQTT retained messages persist on the broker’s filesystem and are delivered to new subscribers. In the main branch, these messages are stored in plaintext within the data/ directory. The protected-no-wildcard branch adds filesystem-level encryption using gocryptfs.

Data Flow with Encryption :

The encryption operates transparently to Mosquitto. The broker reads and writes to data/ as if it were a normal directory, while gocryptfs handles encryption/decryption automatically. The actual encrypted data resides in data-encrypted/, which is stored on the Docker host filesystem.

Key characteristics:

  • Algorithm : AES-256-GCM authenticated encryption
  • Scope : Encrypts retained messages only (not in-flight messages)
  • Performance : Minimal overhead for typical MQTT workloads
  • Recovery : Requires the encryption password to mount the filesystem

For implementation details and configuration, see Encrypted Retained Messages.

Sources : README.md:8-9

Auto-Save Retained Messages

The protected-no-wildcard branch includes functionality to automatically persist retained messages to disk after every message write. This contrasts with Mosquitto’s default behavior where:

  • In-memory messages may be lost on unclean shutdown
  • Persistence is typically triggered by shutdown signals or intervals

The auto-save feature ensures that:

  1. Each retained message write triggers a disk synchronization
  2. Retained messages survive unexpected container restarts
  3. Message persistence guarantees are stronger in high-availability scenarios

Trade-offs :

  • Pros : Better data durability, reduced risk of message loss
  • Cons : Increased I/O operations, potential performance impact for high-volume retained message workloads

Sources : README.md9

Branch Comparison and Migration

The following table summarizes when to use each branch:

ScenarioRecommended BranchRationale
Local developmentmainSimpler configuration, no authentication overhead
Single trusted usermainAnonymous access is sufficient for trusted environments
IoT prototypingmainRapid iteration without user management
Multi-tenant deploymentprotected-no-wildcardTopic isolation prevents cross-user access
Production with sensitive dataprotected-no-wildcardEncryption protects retained messages at rest
High-availability requirementsprotected-no-wildcardAuto-save ensures message persistence
Public-facing brokerprotected-no-wildcardAuthentication and ACLs prevent abuse

Migration Path : Moving from main to protected-no-wildcard requires:

  1. Adding user authentication credentials to Mosquitto configuration
  2. Creating an ACL file with appropriate topic rules
  3. Configuring and initializing the gocryptfs encrypted filesystem
  4. Updating client applications to provide authentication credentials
  5. Restructuring topic hierarchies to follow the username-first pattern

Sources : README.md:5-11

Accessing Advanced Features

To explore or deploy the advanced features:

  1. View the diff : Compare the branches to understand specific changes

  2. Checkout the branch :

  3. Review documentation : Each subsection below provides detailed configuration instructions:

Sources : README.md11

Configuration File Locations

The following files are specific to the protected-no-wildcard branch and do not exist in main:

File PathPurposeBranch
mosquitto/aclfileDefines user-based topic access rulesprotected-no-wildcard only
mosquitto/passwordfileStores hashed user credentialsprotected-no-wildcard only
docker-compose.yml (modified)Includes gocryptfs service definitionprotected-no-wildcard only
data-encrypted/Encrypted filesystem storage directoryprotected-no-wildcard only

Files that exist in both branches but have different configurations:

File PathMain BranchProtected Branch
mosquitto.conf2allow_anonymous trueallow_anonymous false
mosquitto.confNo acl_file directiveIncludes acl_file /mosquitto/config/aclfile
mosquitto.confNo password_file directiveIncludes password_file /mosquitto/config/passwordfile

Sources : README.md7 mosquitto.conf:1-6


Next Steps : For detailed implementation guides for each advanced feature, proceed to the subsections:

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Protected Branch Features

Loading…

Protected Branch Features

Relevant source files

Purpose and Scope

This document describes the advanced security features available in the protected-no-wildcard branch of this repository. These features provide enhanced access control, message encryption, and persistence capabilities beyond the base configuration in the main branch.

The protected branch implements three key enhancements:

  1. ACL-based wildcard topic restrictions
  2. Encrypted storage for retained messages
  3. Automatic persistence of retained messages

For basic deployment and configuration information, see Getting Started. For general production deployment considerations, see Production Deployment Considerations.

Sources : README.md:7-13


Overview of the Protected Branch

The protected-no-wildcard branch provides an alternative deployment configuration that addresses specific security and privacy requirements for multi-tenant or privacy-sensitive MQTT deployments. Unlike the main branch which provides anonymous access and no encryption, the protected branch implements defense-in-depth security measures.

Branch Access

ResourceLocation
Protected Branch[protected-no-wildcard branch](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/protected-no-wildcard branch)
Diff from Main[main…protected-no-wildcard comparison](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/main…protected-no-wildcard comparison)
ACL Filemosquitto/aclfile (in protected branch)

The protected branch maintains the same container architecture and Cloudflare Tunnel integration as the main branch, adding security layers within the Mosquitto container and its configuration.

Sources : README.md:7-13


Wildcard Restriction via ACL File

Access Control List Implementation

The protected branch implements topic-level access control using Mosquitto’s ACL (Access Control List) system. The ACL file enforces a topic namespace convention where the first level of each topic path represents a username, preventing users from subscribing to wildcard patterns that cross user boundaries.

Topic Namespace Convention

<username>/<application>/<device>/<metric>

In this pattern:

  • <username> is the first-level topic segment
  • Users can only access topics beginning with their own username
  • Wildcard subscriptions like # or +/+/+ are restricted to prevent cross-user data access
flowchart TD
 
   Client["MQTT Client"] -->|SUBSCRIBE user1/sensors/#| CloudflareTunnel["Cloudflare Tunnel"]
CloudflareTunnel -->|Forward to mosquitto:9001| Cloudflared["cloudflared Container"]
Cloudflared --> MosquittoListener["Mosquitto WebSocket Listener\nPort 9001"]
MosquittoListener --> ACLCheck{"ACL Permission Check"}
ACLCheck -->|Load Rules| ACLFile["mosquitto/aclfile"]
ACLFile -->|Pattern Matching| ACLCheck
    
 
   ACLCheck -->|Allowed| TopicTree["Topic Subscription Tree"]
ACLCheck -->|Denied| RejectSub["SUBACK with Failure Code"]
TopicTree --> MessageRoute["Route Messages to Client"]
RejectSub --> Client
 
   MessageRoute --> Client
    
 
   Publisher["Publishing Client"] -->|PUBLISH user2/data| CloudflareTunnel
 
   CloudflareTunnel --> Cloudflared
 
   Cloudflared --> MosquittoListener
 
   MosquittoListener --> ACLCheckPub{"ACL Publish Check"}
ACLCheckPub -->|Read aclfile| ACLFile
 
   ACLCheckPub -->|Allowed| TopicTree
 
   ACLCheckPub -->|Denied| RejectPub["PUBACK with Error"]
RejectPub --> Publisher

ACL Enforcement Flow

Diagram : ACL enforcement flow showing how the aclfile mediates access to topics based on username-prefixed patterns.

Sources : README.md9


Naive Implementation Characteristics

The README describes this ACL implementation as “naive” because it relies on a simple convention rather than cryptographic authentication:

CharacteristicImplementation Detail
AuthenticationUsers are not cryptographically authenticated; ACL assumes username is provided correctly
Namespace ConventionFirst-level topic segment must match username
Wildcard BlockingPrevents # and multi-level + patterns that span users
Trust ModelAssumes clients accurately identify themselves

This approach provides separation of concerns and data isolation in scenarios where clients are trusted or authenticated through external mechanisms (e.g., Cloudflare Access policies, API tokens in headers).

Sources : README.md9


Encrypted Retained Messages with gocryptfs

flowchart TB
    subgraph "Docker Host Filesystem"
        EncryptedVol["Encrypted Volume\ngocryptfs ciphertext"]
end
    
    subgraph "mosquitto Container"
        GocryptfsMount["gocryptfs FUSE Mount"]
PlaintextView["Plaintext View\n/mosquitto/data"]
MosquittoProcess["Mosquitto Process"]
PersistenceDir["persistence_location\nConfiguration Directive"]
end
    
    subgraph "Message Lifecycle"
        RetainedMsg["Retained Message\nMQTT PUBLISH with retain=true"]
WriteOperation["Write to Persistence Store"]
ReadOperation["Read from Persistence Store"]
DecryptedMsg["Decrypted Message\nDelivered to Subscriber"]
end
    
 
   EncryptedVol <-->|FUSE System Calls| GocryptfsMount
 
   GocryptfsMount -->|Transparent Encryption| PlaintextView
 
   PlaintextView <--> MosquittoProcess
 
   MosquittoProcess -->|References| PersistenceDir
    
 
   RetainedMsg --> MosquittoProcess
 
   MosquittoProcess --> WriteOperation
 
   WriteOperation -->|Plaintext Write| PlaintextView
 
   PlaintextView -->|Encrypted on Disk| GocryptfsMount
    
 
   GocryptfsMount -->|Decrypt on Read| PlaintextView
 
   PlaintextView --> ReadOperation
 
   ReadOperation --> MosquittoProcess
 
   MosquittoProcess --> DecryptedMsg

Overview

The protected branch integrates gocryptfs to encrypt Mosquitto’s retained message store at rest. This prevents unauthorized access to message content if the underlying storage volume is compromised or accessed outside the container.

gocryptfs Integration Architecture

Diagram : gocryptfs provides transparent encryption/decryption between Mosquitto’s plaintext operations and the encrypted on-disk storage.

Sources : README.md10


Encryption Properties

PropertyValue
Encryption TypeFilesystem-level encryption via FUSE
CipherDetermined by gocryptfs (typically AES-256-GCM)
Key StorageConfigured at gocryptfs mount initialization
PerformanceFUSE overhead; transparent to Mosquitto
ScopeOnly retained messages in persistence_location

Data Flow: Publishing a Retained Message

Diagram : Sequence showing how a retained message is transparently encrypted during the persistence operation.

Sources : README.md:10-11


Auto-Save Retained Messages

Persistence Trigger Mechanism

The protected branch configures Mosquitto to automatically save retained messages to persistent storage after every message operation. This differs from the default behavior where Mosquitto may defer writes to optimize performance.

Configuration Directive

The auto-save behavior is controlled by the autosave_interval directive in mosquitto.conf:

autosave_interval 1

Setting autosave_interval to 1 instructs Mosquitto to write the in-memory retained message database to disk after every message change (publish or removal of a retained message).

Sources : README.md11


Auto-Save Behavior Comparison

ConfigurationBehaviorUse Case
Main BranchDefault interval (1800 seconds)Standard deployments with container restart tolerance
Protected BranchInterval of 1 secondData-critical deployments requiring immediate persistence

Auto-Save State Machine

Diagram : State machine showing auto-save behavior triggered after each retained message operation.

Sources : README.md11


Performance Implications

AspectImpact
Disk I/OIncreased write operations on every retained message
LatencyMinimal impact on PUBACK latency (async write)
DurabilityNear-zero data loss on container crash
ThroughputMay limit sustained retained message publish rate
flowchart LR
    subgraph "Main Branch Architecture"
        MainMosq["mosquitto Container\n- Standard config\n- Anonymous access\n- No encryption"]
MainCF["cloudflared Container\n- Standard tunnel"]
MainVol["Volume: ./mosquitto.conf"]
MainVol --> MainMosq
 
       MainCF --> MainMosq
    end
    
    subgraph "Protected Branch Architecture"
        ProtMosq["mosquitto Container\n- ACL enabled\n- autosave_interval=1\n- gocryptfs mount"]
ProtCF["cloudflared Container\n- Standard tunnel"]
ProtACL["Volume: ./mosquitto/aclfile"]
ProtConf["Volume: ./mosquitto.conf\n+ ACL directives"]
ProtCrypt["Encrypted Volume\ngocryptfs"]
ProtACL --> ProtMosq
 
       ProtConf --> ProtMosq
 
       ProtCrypt <--> ProtMosq
 
       ProtCF --> ProtMosq
    end

For deployments with high-frequency retained message updates, the default autosave_interval may provide better throughput at the cost of potential data loss during abnormal termination.


Architectural Comparison: Main vs Protected Branch

Container Architecture Differences

Diagram : Comparison of container architectures between main and protected branches, highlighting additional components in the protected configuration.

Sources : README.md:7-13


Feature Matrix

FeatureMain BranchProtected Branch
Access ControlAnonymous, unrestrictedACL-based, username-scoped
Wildcard SubscriptionsAllowed globallyRestricted to user namespace
Retained Message EncryptionNonegocryptfs transparent encryption
Persistence IntervalDefault (30 minutes)Immediate (1 second)
Additional FilesNonemosquitto/aclfile
Container ComplexityMinimalModerate (gocryptfs setup)
Suitable ForDevelopment, trusted networksMulti-tenant, privacy-sensitive

flowchart TD
    subgraph "User Isolation"
        User1["User: alice"]
User2["User: bob"]
User3["User: charlie"]
end
    
    subgraph "Topic Namespace"
        T1["alice/home/temperature"]
T2["alice/home/humidity"]
T3["bob/sensors/motion"]
T4["bob/sensors/light"]
T5["charlie/devices/status"]
end
    
    subgraph "ACL Enforcement"
        ACL["mosquitto/aclfile\npattern: topic readwrite alice/#\npattern: topic readwrite bob/#\npattern: topic readwrite charlie/#"]
end
    
 
   User1 -->|Allowed| T1
 
   User1 -->|Allowed| T2
 
   User1 -.->|Denied| T3
 
   User1 -.->|Denied| T4
    
 
   User2 -->|Allowed| T3
 
   User2 -->|Allowed| T4
 
   User2 -.->|Denied| T1
    
 
   User3 -->|Allowed| T5
 
   User3 -.->|Denied| T1
    
 
   ACL -.->|Enforces| User1
 
   ACL -.->|Enforces| User2
 
   ACL -.->|Enforces| User3

Use Cases for Protected Branch Features

Multi-Tenant Deployments

In scenarios where multiple users or applications share a single MQTT broker, the ACL-based wildcard restrictions prevent data leakage across tenant boundaries:

Diagram : ACL enforcement creating isolated topic namespaces for multiple users.


Privacy-Sensitive Applications

For deployments handling sensitive data (healthcare, financial, personal information), the gocryptfs encryption ensures that retained messages stored on disk cannot be read without the encryption key:

Use Case: Healthcare IoT
- Medical devices publish patient vitals as retained messages
- Disk snapshots or backups contain only encrypted ciphertext
- Compromise of storage volume does not expose patient data
- Encryption key is managed separately from storage

Data Integrity Critical Systems

The auto-save mechanism ensures minimal data loss in crash scenarios:

ScenarioMain Branch ImpactProtected Branch Impact
Container crashUp to 30 minutes of retained messages lostMaximum 1 second of messages lost
System power lossUp to 30 minutes of retained messages lostMaximum 1 second of messages lost
Normal shutdownAll messages persistedAll messages persisted

Migration Between Branches

Switching from Main to Protected

To adopt the protected branch features:

  1. Review ACL requirements : Determine if your topic structure follows username-prefixed convention
  2. Configure gocryptfs : Initialize encrypted volume and obtain encryption key
  3. Update docker-compose.yml : Modify to mount gocryptfs volume and ACL file
  4. Test ACL rules : Verify wildcard restrictions work as expected
  5. Backup unencrypted data : Retained messages in main branch are plaintext

Switching from Protected to Main

To revert to the simpler main branch configuration:

  1. Decrypt retained messages : Use gocryptfs to access plaintext before migration
  2. Export critical data : Publish non-retained messages if needed for recovery
  3. Remove ACL restrictions : Understand that all topics become globally accessible
  4. Switch branch : Check out main branch and restart containers

Sources : README.md13


Configuration Files in Protected Branch

Additional Files

The protected branch introduces files not present in main:

FilePurposeLocation
mosquitto/aclfileACL pattern definitionsMounted as volume in mosquitto container
Modified mosquitto.confReferences ACL file, sets autosave_interval=1Replaces main branch version
gocryptfs initialization scriptsSet up encrypted filesystemContainer initialization

Modified Configuration Directives

Expected changes to mosquitto.conf in protected branch:

# ACL Configuration
acl_file /mosquitto/config/aclfile

# Persistence Configuration
autosave_interval 1
persistence true
persistence_location /mosquitto/data/

Sources : README.md:9-11


Security Considerations

Threat Model

The protected branch defends against specific threats:

ThreatMitigation
Cross-tenant data accessACL wildcard restrictions
Disk volume compromisegocryptfs encryption at rest
Data loss on crashAuto-save immediate persistence
Unauthorized topic subscriptionACL pattern matching

Limitations

The protected branch does NOT protect against:

  • Man-in-the-middle attacks (handled by Cloudflare Tunnel TLS)
  • Compromised MQTT clients (they can still access their own namespace)
  • Memory-resident message inspection (encryption only at rest)
  • DoS attacks from authenticated users

For comprehensive security, combine protected branch features with:

  • Cloudflare Access policies (see Security Model)
  • MQTT authentication plugins
  • Rate limiting and message size restrictions
  • Network-level monitoring

Sources : README.md9


Summary

The protected-no-wildcard branch extends the base Docker MQTT Mosquitto deployment with three integrated security features:

  1. ACL-based wildcard restrictions isolate user topics using username-prefixed patterns
  2. gocryptfs encryption protects retained messages at rest with transparent filesystem encryption
  3. Auto-save persistence minimizes data loss by writing retained messages after every operation

These features are particularly valuable for multi-tenant deployments, privacy-sensitive applications, and systems requiring high data durability. The trade-offs include increased container complexity, potential performance impact from frequent disk writes, and operational overhead of managing ACL files and encryption keys.

For standard single-tenant or development deployments, the main branch provides a simpler configuration with anonymous access and no encryption. For production deployments requiring enhanced security and isolation, the protected branch implements defense-in-depth measures while maintaining the same Cloudflare Tunnel integration and container orchestration model.

Sources : README.md:7-13

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Production Deployment Considerations

Loading…

Production Deployment Considerations

Relevant source files

Purpose and Scope

This document provides technical guidance for deploying the Docker MQTT Mosquitto with Cloudflare Tunnel system in production environments. It covers security hardening, scalability, high availability, resource management, monitoring, and disaster recovery strategies that extend beyond the basic development setup.

For information about the advanced security features in the alternative branch, see Protected Branch Features. For monitoring implementation details, see Monitoring and Health Checks. For secret management during development, see Version Control Best Practices.

The development configuration provided in docker-compose.yml:1-18 is suitable for testing and proof-of-concept deployments, but requires several modifications for production use. This document identifies these gaps and provides implementation guidance.


Security Hardening

Authentication and Authorization

The default configuration in mosquitto.conf2 sets allow_anonymous true, which permits unauthenticated client connections. This is unacceptable for production deployments.

Production Configuration Requirements:

Security ControlDevelopmentProduction Required
Anonymous AccessEnabledDisabled
Password AuthenticationNoneRequired
ACL (Access Control Lists)NoneTopic-level restrictions
TLS EncryptionCloudflare-managedEnd-to-end recommended
Connection LimitsUnlimitedRate-limited

Implementing Password Authentication

Modify mosquitto.conf:1-6 to include authentication:

listener 1883
allow_anonymous false
password_file /mosquitto/config/password_file

listener 9001
protocol websockets
allow_anonymous false
password_file /mosquitto/config/password_file

Create the password file using mosquitto_passwd:

Update docker-compose.yml:7-8 to mount the password file:

Implementing Topic-Based ACL

Create an ACL file to restrict topic access per user. The [protected-no-wildcard branch](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/protected-no-wildcard branch) demonstrates a username-based topic hierarchy where the first topic level represents the username.

Production ACL Pattern:

# Admin users - full access
user admin
topic readwrite #

# IoT devices - restricted to device-specific topics
user device001
topic readwrite devices/device001/#

user device002
topic readwrite devices/device002/#

# Read-only monitoring users
user monitor
topic read #

Sources: mosquitto.conf:1-6 README.md:9-10


Secret Management

The development approach using .env files is insufficient for production. The CLOUDFLARE_TUNNEL_TOKEN in docker-compose.yml17 grants tunnel routing access and must be protected.

Production Secret Management Flow

Kubernetes Secret Management Example:

Docker Swarm Secret Management Example:

Sources: docker-compose.yml:14-17 .env.sample1 .gitignore1


Network Security

The current configuration in docker-compose.yml:1-18 does not implement network policies. Production deployments should restrict inter-container communication.

graph TB
    subgraph "External Network"
        CF_EDGE["Cloudflare Edge Network"]
end
    
    subgraph "Docker Host"
        subgraph "frontend_network"
            CFD["cloudflared\ncontainer_name: cloudflared\nimage: cloudflare/cloudflared:latest"]
end
        
        subgraph "backend_network"
            MOSQ["mosquitto\ncontainer_name: mosquitto\nimage: eclipse-mosquitto:latest"]
end
        
        subgraph "Shared Network: tunnel_network"
            CFD_BRIDGE["cloudflared bridge"]
MOSQ_BRIDGE["mosquitto bridge"]
end
    end
    
 
   CF_EDGE <-->|outbound tunnel| CFD
 
   CFD_BRIDGE <-->|port 9001| MOSQ_BRIDGE
 
   CFD -.->|connected to tunnel_network| CFD_BRIDGE
 
   MOSQ -.->|connected to tunnel_network| MOSQ_BRIDGE

Docker Network Segmentation

Production docker-compose.yml Network Configuration:

Key Security Enhancements:

  • read_only: true: Container filesystems are read-only
  • security_opt: no-new-privileges: Prevents privilege escalation
  • tmpfs: /tmp: Writable temporary directory
  • Named volumes: Persistent data storage
  • Static IP addresses: Predictable network topology

Sources: docker-compose.yml:1-18


Persistent Storage and Data Management

The development configuration in docker-compose.yml:4-9 does not provision persistent volumes for Mosquitto data. Message retention, subscriptions, and authentication data require persistent storage.

graph LR
    subgraph "Host Filesystem"
        COMPOSE["docker-compose.yml"]
MOSQ_CONF_FILE["mosquitto.conf"]
end
    
    subgraph "Docker Volumes"
        DATA_VOL["mosquitto_data\n(retained messages,\nsubscriptions)"]
LOG_VOL["mosquitto_log\n(broker logs)"]
CONFIG_VOL["config volume\n(mounted configs)"]
end
    
    subgraph "mosquitto Container"
        MOSQ_PROC["mosquitto process"]
DATA_MOUNT["/mosquitto/data"]
LOG_MOUNT["/mosquitto/log"]
CONF_MOUNT["/mosquitto/config"]
end
    
    subgraph "Backup Systems"
        BACKUP_CRON["cron job"]
BACKUP_STORAGE["Remote Storage\n(S3, NFS, etc.)"]
end
    
 
   MOSQ_CONF_FILE -->|bind mount| CONFIG_VOL
 
   CONFIG_VOL -->|mounted at| CONF_MOUNT
 
   DATA_VOL -->|mounted at| DATA_MOUNT
 
   LOG_VOL -->|mounted at| LOG_MOUNT
    
 
   CONF_MOUNT --> MOSQ_PROC
 
   DATA_MOUNT --> MOSQ_PROC
 
   LOG_MOUNT --> MOSQ_PROC
    
 
   DATA_VOL -.->|backup| BACKUP_CRON
 
   LOG_VOL -.->|backup| BACKUP_CRON
 
   BACKUP_CRON -->|archive| BACKUP_STORAGE

Storage Architecture

Volume Configuration for Production

Modify docker-compose.yml:7-8 to include persistent volumes:

Backup Strategy

Automated Backup Script:

Restoration Procedure:

Sources: docker-compose.yml:7-8


Resource Management and Scaling

Container Resource Limits

The current docker-compose.yml:4-18 does not specify resource constraints. Production deployments must prevent resource exhaustion.

Production Resource Configuration:

graph TB
    subgraph "Host Resources"
        CPU["CPU Cores"]
MEMORY["System Memory"]
DISK["Disk I/O"]
end
    
    subgraph "Container Allocations"
        MOSQ_CPU["mosquitto\nCPUs: 1.0\nMemory: 1GB\nreservations.memory: 512MB"]
CFD_CPU["cloudflared\nCPUs: 0.5\nMemory: 512MB\nreservations.memory: 256MB"]
end
    
    subgraph "Monitoring Limits"
        ALERTS["Resource Alerts\n(>80% usage)"]
METRICS["Prometheus Metrics\ncontainer_memory_usage_bytes\ncontainer_cpu_usage_seconds_total"]
end
    
 
   CPU -->|allocated| MOSQ_CPU
 
   CPU -->|allocated| CFD_CPU
 
   MEMORY -->|allocated| MOSQ_CPU
 
   MEMORY -->|allocated| CFD_CPU
    
 
   MOSQ_CPU -.->|export metrics| METRICS
 
   CFD_CPU -.->|export metrics| METRICS
 
   METRICS -->|trigger| ALERTS

Mosquitto Performance Tuning

Extend mosquitto.conf:1-6 with production performance settings:

# Connection limits
max_connections 1000
max_queued_messages 1000
max_inflight_messages 20

# Persistence settings
persistence true
persistence_location /mosquitto/data/
autosave_interval 300
autosave_on_changes false

# Memory management
max_keepalive 60
message_size_limit 0

# Logging (reduced verbosity for production)
log_dest file /mosquitto/log/mosquitto.log
log_type error
log_type warning
log_timestamp true

# Listeners
listener 1883
allow_anonymous false
password_file /mosquitto/config/password_file

listener 9001
protocol websockets
allow_anonymous false
password_file /mosquitto/config/password_file

Key Configuration Parameters:

ParameterDevelopmentProductionPurpose
max_connectionsUnlimited1000Prevent resource exhaustion
persistenceFalse (default)TrueRetain messages across restarts
autosave_intervalN/A300Save retained messages every 5 minutes
max_keepalive6553560Detect dead connections faster
log_typeAllerror, warningReduce log volume

Sources: mosquitto.conf:1-6 docker-compose.yml:4-9


High Availability and Clustering

Multi-Instance Deployment Architecture

The single-instance architecture in docker-compose.yml:4-9 provides no redundancy. Production systems require high availability.

Cloudflare Load Balancer Configuration:

  1. Create multiple Cloudflare Tunnels (one per region/instance)
  2. Configure DNS load balancing in Cloudflare dashboard:
    • Geographic routing: Route based on client location
    • Health checks: Monitor tunnel availability
    • Failover: Automatic failover to healthy tunnels

Multi-Tunnel docker-compose.yml:

Mosquitto Bridge Configuration

Configure message synchronization between instances by adding to mosquitto.conf:

# Bridge configuration for message replication
connection bridge-to-replica
address mosquitto-replica:1883
topic # both 0
cleansession false
try_private false
bridge_attempt_unsubscribe true
bridge_protocol_version mqttv311

Sources: docker-compose.yml:4-18 mosquitto.conf:1-6


Container Orchestration

Kubernetes Deployment

For production-scale deployments, replace docker-compose.yml:1-18 with Kubernetes manifests.

Kubernetes Deployment Manifest (mosquitto):

Kubernetes Deployment Manifest (cloudflared):

PersistentVolumeClaim:

Sources: docker-compose.yml:1-18


Monitoring and Logging

Logging Configuration

The default mosquitto.conf:1-6 does not configure logging. Production deployments require comprehensive logging.

Production Logging Configuration (mosquitto.conf):

graph LR
    subgraph "Log Sources"
        MOSQ_PROC["mosquitto process"]
CFD_PROC["cloudflared process"]
DOCKER_DAEMON["Docker Daemon"]
end
    
    subgraph "Log Collection"
        MOSQ_LOG_VOL["/mosquitto/log/mosquitto.log"]
DOCKER_JSON_LOG["container stdout/stderr\n(JSON driver)"]
end
    
    subgraph "Log Aggregation"
        FLUENTD["Fluentd\n(log shipper)"]
FILEBEAT["Filebeat\n(log shipper)"]
end
    
    subgraph "Log Storage & Analysis"
        ELK["Elasticsearch\n(log indexing)"]
KIBANA["Kibana\n(visualization)"]
LOKI["Grafana Loki\n(log aggregation)"]
SPLUNK["Splunk\n(SIEM)"]
end
    
    subgraph "Alerting"
        ALERT_MGR["AlertManager\n(alert routing)"]
PAGERDUTY["PagerDuty"]
SLACK["Slack"]
end
    
 
   MOSQ_PROC -->|writes to| MOSQ_LOG_VOL
 
   MOSQ_PROC -->|stdout| DOCKER_JSON_LOG
 
   CFD_PROC -->|stdout| DOCKER_JSON_LOG
 
   DOCKER_DAEMON -->|container logs| DOCKER_JSON_LOG
    
 
   MOSQ_LOG_VOL -->|tail| FLUENTD
 
   DOCKER_JSON_LOG -->|docker logs| FILEBEAT
    
 
   FLUENTD --> ELK
 
   FILEBEAT --> ELK
 
   FLUENTD --> LOKI
    
 
   ELK --> KIBANA
 
   LOKI --> ALERT_MGR
 
   ELK --> ALERT_MGR
    
 
   ALERT_MGR --> PAGERDUTY
 
   ALERT_MGR --> SLACK
# Comprehensive logging
log_dest file /mosquitto/log/mosquitto.log
log_dest stdout

log_type error
log_type warning
log_type notice
log_type information
log_type subscribe
log_type unsubscribe

log_timestamp true
log_timestamp_format %Y-%m-%dT%H:%M:%S

connection_messages true

Docker Logging Driver Configuration:

Metrics and Monitoring

Implement Prometheus-compatible metrics exporters:

docker-compose.yml with Monitoring:

Prometheus Configuration (prometheus.yml):

Key Metrics to Monitor:

MetricDescriptionAlert Threshold
mosquitto_connected_clientsActive client connections> 80% of max_connections
mosquitto_messages_received_totalTotal messages receivedRate < 1/min (potential downtime)
mosquitto_messages_sent_totalTotal messages sentRate < 1/min (potential issue)
mosquitto_retained_messagesNumber of retained messages> 90% of storage capacity
container_memory_usage_bytesContainer memory usage> 80% of limit
container_cpu_usage_seconds_totalContainer CPU usage> 80% of limit

Sources: mosquitto.conf:1-6 docker-compose.yml:1-18


Disaster Recovery and Business Continuity

Backup Automation

Implement automated backup schedules using cron or Kubernetes CronJobs.

Kubernetes CronJob for Backups:

Recovery Time Objectives

Failure ScenarioRTO (Recovery Time Objective)RPO (Recovery Point Objective)Recovery Procedure
Container crash< 1 minute0 (no data loss)Automatic restart via restart: unless-stopped
Node failure< 5 minutes0 (no data loss)Kubernetes pod rescheduling
Data corruption< 30 minutes24 hoursRestore from latest backup
Regional outage< 15 minutes0 (no data loss)Cloudflare automatic failover
Complete disaster< 2 hours24 hoursDeploy new infrastructure, restore backups

Disaster Recovery Testing

Quarterly DR Test Procedure:

  1. Simulate Container Failure:

  2. Simulate Data Corruption:

  3. Simulate Network Partition:

  4. Verify Backup Integrity:

Sources: docker-compose.yml9 docker-compose.yml15


Security Compliance and Hardening

Container Image Security

The default images specified in docker-compose.yml5 and docker-compose.yml12 should be validated and scanned for vulnerabilities.

Image Security Checklist:

Security ControlImplementation
Base Image VerificationUse official images with verified publishers
Vulnerability ScanningRun docker scan or Trivy before deployment
Image SigningVerify Docker Content Trust signatures
Version PinningUse specific version tags, not latest
Minimal BasePrefer Alpine-based images
Read-Only FilesystemSet read_only: true in docker-compose.yml

Production Image References:

Security Scanning Integration

Add security scanning to CI/CD pipeline:

Compliance Requirements

GDPR/Privacy Compliance:

  • Enable audit logging in mosquitto.conf:1-6
  • Implement data retention policies
  • Configure message encryption
  • Document data flows

SOC 2 Compliance:

  • Implement access controls via ACL
  • Enable comprehensive logging
  • Implement change management procedures
  • Document disaster recovery procedures

PCI DSS (if handling payment data):

  • Implement network segmentation
  • Enable encryption in transit and at rest
  • Implement strong authentication
  • Regular security assessments

Sources: docker-compose.yml5 docker-compose.yml12 mosquitto.conf:1-6


Performance Optimization

Connection Pooling and Load Distribution

The docker-compose.yml:4-9 single-instance architecture does not scale for high-throughput scenarios.

Mosquitto Performance Benchmarks

Expected Performance Metrics:

MetricDevelopment (Single Instance)Production (Clustered)
Max Concurrent Connections~1,000~10,000
Messages/sec (publish)~5,000~50,000
Messages/sec (subscribe)~10,000~100,000
Latency (p99)< 100ms< 50ms
Memory per connection~2KB~2KB

Performance Testing:

Tuning mosquitto.conf for High Throughput

# High-performance configuration
listener 1883
max_connections 10000
max_queued_messages 10000
max_inflight_messages 100

# Disable persistence for high-throughput, low-retention scenarios
# (Use only if message loss is acceptable)
persistence false

# Reduce keepalive overhead
max_keepalive 30

# Increase message size limit (default: 268435456 bytes)
message_size_limit 1048576

# Optimize memory usage
memory_limit 2147483648

# Queue settings
max_queued_bytes 0
queue_qos0_messages false

# Websocket settings
listener 9001
protocol websockets
websocket_timeout 300

Sources: mosquitto.conf:1-6 docker-compose.yml:4-9


Deployment Checklist

Pre-Production Validation

Infrastructure Readiness:

  • Container orchestration platform configured (Kubernetes/Docker Swarm)
  • Persistent storage provisioned with backup strategy
  • Secrets management system configured
  • Monitoring and logging infrastructure deployed
  • Disaster recovery procedures documented and tested

Security Configuration:

  • Authentication enabled in mosquitto.conf2
  • ACL file configured with topic-level restrictions
  • Anonymous access disabled
  • TLS certificates provisioned (if not using Cloudflare Tunnel)
  • Network policies implemented
  • Container security contexts configured (read_only, no-new-privileges)
  • Image vulnerability scanning completed

Cloudflare Configuration:

  • Multiple tunnels created for high availability
  • DNS load balancing configured
  • Health checks enabled
  • Rate limiting configured
  • DDoS protection verified
  • WAF rules configured

Performance Tuning:

Operational Readiness:

  • Backup automation tested
  • Restore procedures validated
  • Monitoring dashboards created
  • Alert rules configured
  • On-call runbooks documented
  • Incident response procedures defined

Compliance:

  • Audit logging enabled
  • Data retention policies implemented
  • Privacy impact assessment completed
  • Security assessment completed
  • Compliance documentation prepared

Sources: docker-compose.yml:1-18 mosquitto.conf:1-6 README.md:1-93


Migration from Development to Production

Transition Strategy

Migration Steps

Phase 1: Staging Environment Setup

  1. Deploy staging environment with production-like configuration
  2. Migrate from .env to secrets manager
  3. Enable authentication and ACL
  4. Configure persistent volumes
  5. Deploy monitoring stack
  6. Run load tests

Phase 2: Production Infrastructure

  1. Provision Kubernetes cluster or production Docker hosts
  2. Configure external secrets management
  3. Set up persistent storage with replication
  4. Deploy monitoring and logging infrastructure
  5. Configure backup automation
  6. Implement network policies

Phase 3: Service Migration

  1. Create multiple Cloudflare Tunnels for production regions
  2. Deploy Mosquitto cluster with bridge configuration
  3. Configure load balancing and health checks
  4. Migrate client connections gradually
  5. Monitor performance and adjust resources
  6. Validate disaster recovery procedures

Phase 4: Optimization

  1. Tune Mosquitto configuration based on production metrics
  2. Adjust resource limits based on actual usage
  3. Optimize backup schedules
  4. Refine alerting thresholds
  5. Document operational procedures

Sources: docker-compose.yml:1-18 mosquitto.conf:1-6 .env.sample1


Conclusion

Production deployment of the Docker MQTT Mosquitto with Cloudflare Tunnel system requires significant enhancements beyond the development configuration in docker-compose.yml:1-18 and mosquitto.conf:1-6 Key production requirements include:

  • Security: Disable anonymous access, implement authentication and ACL, use secrets managers
  • Reliability: Deploy multiple instances, implement health checks, configure automated backups
  • Scalability: Use container orchestration (Kubernetes), implement load balancing, tune resource limits
  • Observability: Deploy comprehensive logging and monitoring, configure alerting, track SLIs/SLOs
  • Operations: Document runbooks, test disaster recovery, implement change management

The transition from development to production should be gradual, with thorough testing in staging environments before production deployment. Regular security assessments, performance tuning, and disaster recovery testing ensure ongoing operational excellence.

Sources: README.md:1-93 docker-compose.yml:1-18 mosquitto.conf:1-6 .env.sample1 .gitignore:1-2

Dismiss

Refresh this wiki

Enter email to refresh


GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Monitoring and Health Checks

Loading…

Monitoring and Health Checks

Relevant source files

Purpose and Scope

This document explains monitoring and health check strategies for the Docker MQTT Mosquitto with Cloudflare Tunnel system. It covers container status verification, automated health checks in the CI pipeline, logging approaches, and techniques for implementing custom monitoring solutions. For production deployment considerations, see Production Deployment Considerations. For troubleshooting specific issues, see Troubleshooting.


Container Status Monitoring

Docker Inspect Command Pattern

The system uses Docker’s native inspection capabilities to verify container health. The primary method queries container state using docker inspect with formatted output.

Container State Check Pattern:

This command returns the current container state, which can be one of: created, restarting, running, removing, paused, exited, or dead.

Container Status Fields

Docker provides multiple state fields that can be inspected for comprehensive health monitoring:

FieldPathDescription
Status.State.StatusCurrent container state
Running.State.RunningBoolean indicating if container is running
ExitCode.State.ExitCodeExit code if container has stopped
StartedAt.State.StartedAtTimestamp when container started
Error.State.ErrorError message if container failed

Sources: .github/workflows/ci.yml30


CI Pipeline Health Verification

Automated Health Check Implementation

The GitHub Actions CI pipeline implements a bounded retry pattern to verify the mosquitto container reaches a healthy running state after startup. This pattern accounts for non-deterministic container initialization times.

flowchart TD
    Start["Start Docker Compose\ndocker-compose up -d mosquitto"]
Init["Initialize retry counter\ni=1, max=10"]
Inspect["Execute docker inspect\n--format='{{.State.Status}}'"]
Check{"Status == 'running'?"}
Success["Log: Mosquitto is running\nExit 0"]
Wait["Log: Waiting for Mosquitto...\nSleep 10 seconds"]
Increment["Increment counter\ni++"]
CheckMax{"i > 10?"}
Timeout["Log: Did not become healthy\nExit 1"]
Start --> Init
 
   Init --> Inspect
 
   Inspect --> Check
 
   Check -->|Yes| Success
 
   Check -->|No| Wait
 
   Wait --> Increment
 
   Increment --> CheckMax
 
   CheckMax -->|No| Inspect
 
   CheckMax -->|Yes| Timeout

Health Check Flow Diagram

Sources: .github/workflows/ci.yml:27-39

Retry Loop Parameters

The health check uses the following parameters:

ParameterValueRationale
Max Attempts10Provides 100 seconds total wait time
Sleep Interval10 secondsBalances responsiveness and system load
Total Timeout100 secondsSufficient for cold starts and image pulls
Check Methoddocker inspectNative Docker API, no external dependencies
Success CriteriaStatus contains runningContainer process is active

The implementation uses a shell loop structure:

Sources: .github/workflows/ci.yml:28-39


Docker Native Healthcheck Configuration

Current Implementation

The system currently relies on Docker Compose’s restart: unless-stopped policy for automatic recovery but does not define explicit healthcheck directives in the compose configuration.

Current Restart Policy:

Adding Native Docker Healthchecks

Docker Compose supports native healthcheck definitions that provide more sophisticated monitoring than simple state checks. Below are example configurations for both services.

Mosquitto Healthcheck Example

This healthcheck:

  • Subscribes to the system topic $SYS/broker/uptime
  • Waits up to 3 seconds for a message
  • Receives 1 message (-C 1) to confirm broker is operational
  • Runs every 30 seconds
  • Allows 10 seconds for the test to complete
  • Requires 3 consecutive failures before marking unhealthy

Cloudflared Healthcheck Example

This healthcheck:

  • Executes cloudflared tunnel info to verify tunnel connectivity
  • Runs every 60 seconds (tunnel state changes slowly)
  • Allows 5 retries (tunnel reconnections may take time)
  • Provides 30 seconds startup grace period

Sources: docker-compose.yml:4-17


System Monitoring Architecture

Container Monitoring Topology

Sources: docker-compose.yml:1-18 .github/workflows/ci.yml:27-39


Cloudflared Tunnel Monitoring

Tunnel Status Verification

The cloudflared container maintains an outbound connection to Cloudflare’s network. Monitoring tunnel health requires checking both container state and tunnel connectivity.

Log-Based Monitoring

The cloudflared process outputs connection status to stdout:

Tunnel Status Commands

Common Cloudflared Log Patterns

Log PatternMeaningAction Required
Registered tunnel connectionTunnel successfully connectedNormal operation
unable to register connectionAuthentication or network failureVerify token and connectivity
Retrying inTemporary connection lossMonitor for recovery
SIGTERM receivedGraceful shutdown initiatedExpected during restarts
certificate errorTLS/SSL verification failedCheck system time and CA certificates

Sources: docker-compose.yml:11-17


Mosquitto Broker Monitoring

MQTT System Topics

The Mosquitto broker publishes operational metrics to reserved $SYS topics. These provide real-time broker statistics without external dependencies.

Key System Topics

TopicDescriptionType
$SYS/broker/versionBroker version stringStatic
$SYS/broker/uptimeSeconds since broker startCounter
$SYS/broker/clients/connectedCurrent connected client countGauge
$SYS/broker/clients/totalTotal clients since startCounter
$SYS/broker/messages/receivedTotal messages receivedCounter
$SYS/broker/messages/sentTotal messages sentCounter
$SYS/broker/bytes/receivedTotal bytes receivedCounter
$SYS/broker/bytes/sentTotal bytes sentCounter

Monitoring via MQTT Client

Log-Based Monitoring

Sources: docker-compose.yml:4-9


Health Check Decision Tree

Sources: .github/workflows/ci.yml:27-42 docker-compose.yml:1-18


Custom Health Check Strategies

Script-Based Monitoring

Implement a shell script for comprehensive health verification:

External Monitoring Integration

Prometheus Exporter Pattern

For production deployments, integrate with monitoring systems using exporters:

  1. Docker Container Exporter : Exposes container metrics
  2. MQTT Exporter : Subscribes to $SYS topics and exposes as Prometheus metrics
  3. Cloudflare Tunnel Exporter : Monitors tunnel status via Cloudflare API

Example Prometheus Configuration

Sources: .github/workflows/ci.yml:27-39


Logging and Log Analysis

Container Log Access

Both containers output logs to stdout/stderr, which Docker captures:

Log Patterns and Indicators

Mosquitto Startup Success Indicators

Opening ipv4 listen socket on port 1883.
Opening ipv4 listen socket on port 9001.
mosquitto version X.X.X running

Cloudflared Connection Success Indicators

Registered tunnel connection
Connection established

Log Aggregation

For production deployments, consider implementing centralized logging:

Sources: docker-compose.yml:4-17


Restart Policies and Recovery

Current Configuration

Both services use the unless-stopped restart policy, which provides:

  • Automatic restart on failure
  • Respect for manual stops (docker-compose stop)
  • Persistence across Docker daemon restarts

Configuration locations:

Monitoring Restart Behavior

Crash Loop Detection

Frequent restarts indicate underlying issues:

If restart count increases rapidly (>3 restarts in 1 minute), investigate:

  1. Check container logs for error messages
  2. Verify configuration file syntax
  3. Confirm environment variables are set
  4. Check resource constraints (memory/CPU limits)

Sources: docker-compose.yml9 docker-compose.yml15


CI Pipeline Integration

The GitHub Actions CI workflow serves as a reference implementation for automated health verification. The workflow demonstrates:

  1. Isolated Environment : Starts only the mosquitto service without dependencies
  2. Bounded Wait : Implements timeout to prevent hanging builds
  3. State Verification : Uses Docker’s native state inspection
  4. Clean Teardown : Ensures resources are released after testing

CI Health Check Sequence

Sources: .github/workflows/ci.yml:24-42


Best Practices Summary

PracticeImplementationBenefit
Bounded retriesMax 10 attempts with 10s intervalPrevents infinite waits
Docker native checksUse docker inspect for stateNo external dependencies
Log monitoringRegularly check container logsEarly problem detection
Restart trackingMonitor RestartCount metricIdentify crash loops
System topicsSubscribe to $SYS/# for MQTT statsBroker-native monitoring
Health scriptsAutomate multi-component checksConsistent verification
External integrationExport metrics to monitoring systemsProduction observability

Sources: .github/workflows/ci.yml:27-39 docker-compose.yml:1-18

Dismiss

Refresh this wiki

Enter email to refresh

On this page

  • Monitoring and Health Checks
  • Purpose and Scope
  • Container Status Monitoring
  • Docker Inspect Command Pattern
  • Container Status Fields
  • CI Pipeline Health Verification
  • Automated Health Check Implementation
  • Health Check Flow Diagram
  • Retry Loop Parameters
  • Docker Native Healthcheck Configuration
  • Current Implementation
  • Adding Native Docker Healthchecks
  • Mosquitto Healthcheck Example
  • Cloudflared Healthcheck Example
  • System Monitoring Architecture
  • Container Monitoring Topology
  • Cloudflared Tunnel Monitoring
  • Tunnel Status Verification
  • Log-Based Monitoring
  • Tunnel Status Commands
  • Common Cloudflared Log Patterns
  • Mosquitto Broker Monitoring
  • MQTT System Topics
  • Key System Topics
  • Monitoring via MQTT Client
  • Log-Based Monitoring
  • Health Check Decision Tree
  • Custom Health Check Strategies
  • Script-Based Monitoring
  • External Monitoring Integration
  • Prometheus Exporter Pattern
  • Example Prometheus Configuration
  • Logging and Log Analysis
  • Container Log Access
  • Log Patterns and Indicators
  • Mosquitto Startup Success Indicators
  • Cloudflared Connection Success Indicators
  • Log Aggregation
  • Restart Policies and Recovery
  • Current Configuration
  • Monitoring Restart Behavior
  • Crash Loop Detection
  • CI Pipeline Integration
  • CI Health Check Sequence
  • Best Practices Summary

GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Troubleshooting

Loading…

Troubleshooting

Relevant source files

Purpose and Scope

This page provides diagnostic procedures and solutions for common issues encountered when deploying and operating the Docker MQTT Mosquitto with Cloudflare Tunnel system. It covers container startup failures, connection problems, configuration errors, and debugging techniques.

For information about the CI/CD automated health checks, see Continuous Integration. For production monitoring strategies, see Monitoring and Health Checks. For initial setup procedures, see Getting Started.


Diagnostic Decision Tree

The following flowchart provides a systematic approach to diagnosing system issues:

Sources : README.md:74-84 docker-compose.yml:1-18 mosquitto.conf:1-6

flowchart TD
 
   Start["Issue Reported"] --> CheckContainers{"docker compose ps\nAll containers running?"}
CheckContainers -->|No| IdentifyDown{"Which container\nis down?"}
CheckContainers -->|Yes| CheckTunnel["Check tunnel connectivity"]
IdentifyDown -->|mosquitto| MosquittoDown["Check mosquitto logs:\ndocker compose logs mosquitto"]
IdentifyDown -->|cloudflared| CloudflaredDown["Check cloudflared logs:\ndocker compose logs cloudflared"]
IdentifyDown -->|Both| BothDown["Check docker-compose.yml\nand .env configuration"]
MosquittoDown --> MosquittoErrors{"Error type?"}
MosquittoErrors -->|Config syntax| CheckMosquittoConf["Validate mosquitto.conf:1-5\nlistener directives"]
MosquittoErrors -->|Port binding| CheckPortConflict["Check port 1883/9001\nconflicts on host"]
MosquittoErrors -->|Volume mount| CheckVolumeMount["Verify ./mosquitto.conf\nfile exists and readable"]
CloudflaredDown --> CloudflaredErrors{"Error type?"}
CloudflaredErrors -->|Authentication failed| CheckToken["Verify CLOUDFLARE_TUNNEL_TOKEN\nin .env file"]
CloudflaredErrors -->|Tunnel not found| RecreateToken["Generate new token\nin Cloudflare dashboard"]
CloudflaredErrors -->|Connection refused| CheckNetwork["Verify internet connectivity\nand firewall rules"]
BothDown --> CheckDockerCompose["Validate docker-compose.yml\nsyntax and structure"]
CheckTunnel --> TunnelTest{"Can connect via\npublic hostname?"}
TunnelTest -->|No| CheckHostname["Verify hostname in\nCloudflare dashboard\npoints to mosquitto:9001"]
TunnelTest -->|Yes| CheckMQTT["Test MQTT connection\non port 443"]
CheckMQTT --> MQTTTest{"MQTT publish/\nsubscribe works?"}
MQTTTest -->|No| CheckProtocol["Verify protocol:\nWebSockets required\nfor tunnel connection"]
MQTTTest -->|Yes| Resolved["Issue resolved"]
CheckMosquittoConf --> Restart["docker compose restart mosquitto"]
CheckPortConflict --> StopConflicting["Stop conflicting service\nor change port mapping"]
CheckVolumeMount --> FixMount["Create/fix mosquitto.conf\nand verify path"]
CheckToken --> UpdateEnv["Update .env and\ndocker compose restart cloudflared"]
RecreateToken --> UpdateEnv
 
   CheckNetwork --> FixNetwork["Check DNS/firewall/proxy"]
CheckDockerCompose --> FixCompose["Fix syntax errors\nand docker compose up"]
CheckHostname --> UpdateHostname["Update hostname config\nin Cloudflare dashboard"]
CheckProtocol --> UseWebSockets["Use wss:// protocol\non port 443"]
Restart --> Resolved
 
   StopConflicting --> Resolved
 
   FixMount --> Resolved
 
   UpdateEnv --> Resolved
 
   FixNetwork --> Resolved
 
   FixCompose --> Resolved
 
   UpdateHostname --> Resolved
 
   UseWebSockets --> Resolved

Common Issues and Solutions

Container Startup Failures

IssueSymptomsDiagnosisSolution
Missing tunnel tokencloudflared container exits immediatelydocker compose logs cloudflared shows authentication errorCreate .env file from .env.sample template and populate CLOUDFLARE_TUNNEL_TOKEN
Invalid tokencloudflared shows “tunnel credentials invalid”Token rejected by Cloudflare APIGenerate new token in Cloudflare Zero Trust dashboard and update .env
Configuration syntax errormosquitto container crashes on startupdocker compose logs mosquitto shows parse errorValidate mosquitto.conf:1-6 syntax, ensure listener directives are properly formatted
Port conflictmosquitto fails to bind portsError: “Address already in use”Identify conflicting service with sudo lsof -i :1883 and sudo lsof -i :9001, then stop conflicting service
Volume mount failuremosquitto cannot read configurationPermission denied or file not found errorVerify ./mosquitto.conf exists and has read permissions: ls -l mosquitto.conf
Image pull failureContainer fails to start with image errorCannot pull eclipse-mosquitto:latest or cloudflare/cloudflared:latestCheck Docker Hub connectivity, verify registry access, or use explicit image versions

Sources : docker-compose.yml:4-17 mosquitto.conf:1-6 .env.sample1


Connection and Tunnel Issues

The following diagram illustrates the connection verification process:

Connection Troubleshooting Table :

Failure PointTest CommandExpected ResultFix
Tunnel not established`docker compose logs cloudflaredgrep “Connection established”`Should see “Connection established” message
Mosquitto not listeningdocker compose exec mosquitto netstat -tlnpShould show :1883 and :9001 listenersCheck mosquitto.conf:1-5 configuration, restart container
DNS resolution failuredocker compose exec cloudflared nslookup mosquittoShould resolve to container IPVerify container_name: mosquitto in docker-compose.yml6
Public hostname misconfiguredCheck Cloudflare dashboard tunnel configurationURL should point to mosquitto:9001 with HTTP service typeUpdate hostname configuration in Cloudflare dashboard
WebSocket protocol mismatchClient connection logs show protocol errorClient must use wss:// protocol on port 443Update client to use WebSockets over SSL/TLS

Sources : docker-compose.yml:11-17 mosquitto.conf:4-5 README.md:63-67


Health Check Verification

The CI pipeline implements a health check strategy that can be replicated for manual troubleshooting:

Manual Health Check Procedure :

sequenceDiagram
    participant User as "User/CI System"
    participant Docker as "docker-compose"
    participant MosqContainer as "mosquitto container"
    participant HealthCheck as "Health Check Loop"
    
    User->>Docker: docker-compose up -d mosquitto
    Docker->>MosqContainer: Start container
    MosqContainer->>MosqContainer: Load /mosquitto/config/mosquitto.conf
    MosqContainer->>MosqContainer: Bind listener 1883
    MosqContainer->>MosqContainer: Bind listener 9001
    
    User->>HealthCheck: Start health check loop
    Note over HealthCheck: Max 10 attempts, 10 second intervals
    
    loop Until healthy or max attempts
        HealthCheck->>Docker: docker-compose ps --format json
        Docker-->>HealthCheck: Container status JSON
        HealthCheck->>HealthCheck: Parse 'State' field
        
        alt State == "running"
            HealthCheck-->>User: ✓ Service healthy
        else State != "running"
            HealthCheck->>HealthCheck: Sleep 10 seconds
            HealthCheck->>HealthCheck: Increment attempt counter
        end
    end
    
    alt Max attempts exceeded
        HealthCheck-->>User: ✗ Health check failed after 100s
        User->>Docker: docker-compose logs mosquitto
        Docker-->>User: Error logs
    end

Sources : .github/workflows/ci.yml:17-37 docker-compose.yml:4-9 mosquitto.conf:1-6


Configuration Validation

Validating docker-compose.yml

The following elements must be correctly configured in docker-compose.yml:1-18:

Validation Commands :

Sources : docker-compose.yml:1-18 mosquitto.conf:1-6 .env.sample1


Validating mosquitto.conf

The mosquitto.conf:1-6 file has a simple structure but specific syntax requirements:

LineDirectivePurposeCommon Errors
1listener 1883Native MQTT protocol portMissing port number, invalid port range (1-65535)
2allow_anonymous trueAuthentication settingTypo in allow_anonymous, missing boolean value
3(blank)SeparatorN/A
4listener 9001WebSocket protocol portDuplicate port number, port conflict with line 1
5protocol websocketsProtocol specification for listenerTypo in websockets, missing protocol directive

Validation Procedure :

Sources : mosquitto.conf:1-6


Environment Variable Issues

Missing or Invalid CLOUDFLARE_TUNNEL_TOKEN

The CLOUDFLARE_TUNNEL_TOKEN environment variable is the critical authentication credential for establishing the tunnel connection.

Common Token Issues :

ProblemSymptomRoot CauseSolution
Token not setcloudflared exits with “missing token” error.env file not created or emptyCopy .env.sample1 to .env and populate token
Token syntax errorAuthentication fails immediatelyToken contains extra spaces, newlines, or quotesEnsure token is on single line with no surrounding quotes: CLOUDFLARE_TUNNEL_TOKEN=eyJh...
Token expired/revoked“unauthorized” or “tunnel not found” errorToken regenerated in Cloudflare dashboard or tunnel deletedGenerate new token from Cloudflare dashboard and update .env
Wrong tokenTunnel connects but routes to wrong serviceToken from different tunnel configurationVerify token matches the tunnel name in Cloudflare dashboard
.env not loadedVariable shows as empty in container.env file not in correct directory or not named exactly .envVerify .env is in same directory as docker-compose.yml

Debugging Environment Variables :

Sources : docker-compose.yml:16-17 .env.sample1 README.md53


flowchart TB
    ViewLogs["View Container Logs"]
ViewLogs --> MosqLogs["docker compose logs mosquitto"]
ViewLogs --> CFDLogs["docker compose logs cloudflared"]
ViewLogs --> AllLogs["docker compose logs"]
MosqLogs --> MosqSuccess["Success Indicators"]
MosqLogs --> MosqErrors["Error Indicators"]
MosqSuccess --> MS1["'Opening ipv4 listen socket on port 1883'"]
MosqSuccess --> MS2["'Opening websockets listen socket on port 9001'"]
MosqSuccess --> MS3["'mosquitto version X.X.X running'"]
MosqErrors --> ME1["'Error: Unable to open config file'\n→ Volume mount issue"]
MosqErrors --> ME2["'Error: Invalid bridge parameter'\n→ Config syntax error"]
MosqErrors --> ME3["'Error: Address already in use'\n→ Port conflict"]
CFDLogs --> CFDSuccess["Success Indicators"]
CFDLogs --> CFDErrors["Error Indicators"]
CFDSuccess --> CS1["'Connection established'"]
CFDSuccess --> CS2["'Registered tunnel connection'"]
CFDSuccess --> CS3["'Serving tunnel'"]
CFDErrors --> CE1["'authentication failed'\n→ Invalid token"]
CFDErrors --> CE2["'tunnel not found'\n→ Token/tunnel mismatch"]
CFDErrors --> CE3["'connection refused'\n→ Network issue"]

Log Analysis

Reading Container Logs

Both containers produce diagnostic output that can be analyzed for troubleshooting:

Log Analysis Commands :

Sources : docker-compose.yml:4-17


Network Diagnostics

Docker Network Verification

Both containers must be on the same Docker network for internal DNS resolution to function:

Sources : docker-compose.yml:1-18 README.md64


Cloudflare Dashboard Verification

Issues may originate from misconfiguration in the Cloudflare Zero Trust dashboard:

Configuration ItemLocationExpected ValueCommon Mistakes
Tunnel typeNetworks → Tunnels → Tunnel detailsCloudflaredWrong tunnel type selected
Tunnel statusNetworks → Tunnels → Tunnel list“HEALTHY” with green indicatorShows as “DOWN” if container not running or token invalid
Public hostnameNetworks → Tunnels → Public Hostnames tabSubdomain and domain configuredMissing or incorrect hostname
Service typePublic hostname configurationHTTPHTTPS selected (creates double encryption issue)
Service URLPublic hostname configurationmosquitto:9001Incorrect hostname (e.g., localhost, IP address) or wrong port

Verification Steps :

  1. Navigate to Cloudflare Zero Trust dashboard
  2. Go to Networks → Tunnels
  3. Locate your tunnel and verify status shows “HEALTHY”
  4. Click tunnel name to view details
  5. Check Public Hostname tab for correct configuration:

Sources : README.md:29-72 docker-compose.yml6 mosquitto.conf:4-5


Client Connection Issues

Protocol and Port Requirements

External clients must use WebSockets over SSL/TLS on port 443:

Client ProtocolPortResult
wss://subdomain.domain443✓ Correct - WebSockets over SSL through tunnel
mqtt://subdomain.domain443✗ Wrong protocol - Native MQTT not supported through tunnel
ws://subdomain.domain443✗ Wrong protocol - Non-SSL WebSocket rejected
wss://subdomain.domain1883✗ Wrong port - Port 1883 not exposed through tunnel
mqtt://localhost1883✓ Works only from Docker host (not through tunnel)

Client Configuration Example :

Testing Connection :

Sources : README.md:66-82 mosquitto.conf:4-5


Advanced Debugging Techniques

Container Introspection

Resource Constraints

Complete System Reset

Sources : docker-compose.yml:1-18



Summary of Common Error Messages

Error MessageFile/ComponentLikely CauseSolution Reference
“Address already in use”mosquittoPort 1883 or 9001 conflict[Container Startup Failures](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/Container Startup Failures)
“Unable to open config file”mosquittoVolume mount issue[Container Startup Failures](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/Container Startup Failures)
“authentication failed”cloudflaredInvalid CLOUDFLARE_TUNNEL_TOKEN[Environment Variable Issues](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/Environment Variable Issues)
“tunnel not found”cloudflaredToken/tunnel mismatch[Environment Variable Issues](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/Environment Variable Issues)
“connection refused”cloudflaredNetwork/firewall issue[Network Diagnostics](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/Network Diagnostics)
“protocol error”ClientUsing mqtt:// instead of wss://[Client Connection Issues](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/Client Connection Issues)
“DNS resolution failed”cloudflaredContainer name mismatch[Network Diagnostics](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/Network Diagnostics)
“Invalid bridge parameter”mosquittoConfig syntax error in mosquitto.conf[Configuration Validation](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/59f1274c/Configuration Validation)

Sources : docker-compose.yml:1-18 mosquitto.conf:1-6 .env.sample1 README.md:1-93

Dismiss

Refresh this wiki

Enter email to refresh