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

Relevant source files

This document provides a high-level introduction to the Docker MQTT Mosquitto Cloudflare Tunnel system. It covers the system's purpose, architecture, key components, and how they interact to provide secure MQTT message brokering accessible over the internet.

For detailed setup instructions, see Getting Started. For in-depth component documentation, see Components. For security architecture details, see Security Model.

System Purpose

The Docker MQTT Mosquitto Cloudflare Tunnel system provides a secure MQTT broker deployment that is accessible from the public internet without exposing ports directly on the host machine. It accomplishes this by combining Eclipse Mosquitto (an MQTT message broker) with Cloudflare Tunnel (a secure proxy service).

The system solves the following challenges:

  • Port Exposure : Eliminates the need for port forwarding or firewall rules to expose MQTT services
  • Secure Access : Provides encrypted transport between external clients and the internal broker through Cloudflare's global network
  • DDoS Protection : Leverages Cloudflare's infrastructure for traffic filtering and protection
  • Easy Deployment : Uses Docker Compose for simplified orchestration and deployment

This system is particularly suited for IoT applications, home automation, and message brokering scenarios where devices need to communicate with a central MQTT broker over the internet.

Sources : README.md:1-21

Key Components

The system consists of two containerized services orchestrated by Docker Compose, plus configuration files and authentication credentials.

graph TB
    subgraph "docker-compose.yml"
        mosquitto_service["mosquitto service"]
cloudflared_service["cloudflared service"]
end
    
    subgraph "Container Images"
        mosquitto_image["eclipse-mosquitto:latest"]
cloudflared_image["cloudflare/cloudflared:latest"]
end
    
    subgraph "Configuration Files"
        mosquitto_conf["mosquitto.conf"]
env_file[".env"]
env_sample[".env.sample"]
end
    
    subgraph "Environment Variables"
        token_var["CLOUDFLARE_TUNNEL_TOKEN"]
end
    
    subgraph "Data Storage"
        data_dir["data/ directory"]
end
    
 
   mosquitto_service -->|uses image| mosquitto_image
 
   mosquitto_service -->|volume mount| mosquitto_conf
 
   mosquitto_service -->|may store data in| data_dir
    
 
   cloudflared_service -->|uses image| cloudflared_image
 
   cloudflared_service -->|reads from| env_file
    
 
   env_file -->|contains| token_var
 
   env_sample -->|template for| env_file
    
 
   token_var -->|authenticates| cloudflared_service

Component Map

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

Service Definitions

ServiceContainer NameImagePurpose
mosquittomosquittoeclipse-mosquitto:latestMQTT message broker with two listeners (TCP and WebSocket)
cloudflaredcloudflaredcloudflare/cloudflared:latestCloudflare Tunnel client that proxies traffic to the broker

Sources : docker-compose.yml:4-17

Configuration Files

FilePurposeVersion Controlled
docker-compose.ymlService orchestration and container definitionsYes
mosquitto.confMosquitto broker configuration (listeners, protocols, access control)Yes
.envEnvironment variables including CLOUDFLARE_TUNNEL_TOKENNo (secret)
.env.sampleTemplate for .env fileYes

Sources : docker-compose.yml:1-18 mosquitto.conf:1-6 README.md51

Service Architecture

The following diagram shows how the two Docker services are configured and how they relate to their configuration sources.

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

graph TB
    subgraph "Docker Compose Services"
        direction LR
        
        subgraph mosquitto["mosquitto service (container_name: mosquitto)"]
mosq_image["image: eclipse-mosquitto:latest"]
mosq_restart["restart: unless-stopped"]
mosq_volume["volume: ./mosquitto.conf:/mosquitto/config/mosquitto.conf"]
end
        
        subgraph cloudflared["cloudflared service (container_name: cloudflared)"]
cf_image["image: cloudflare/cloudflared:latest"]
cf_command["command: tunnel --no-autoupdate run --token"]
cf_restart["restart: unless-stopped"]
cf_env["environment: CLOUDFLARE_TUNNEL_TOKEN"]
end
    end
    
    subgraph "Configuration Sources"
        conf_file["mosquitto.conf\n- listener 1883\n- listener 9001 (websockets)\n- allow_anonymous true"]
env_file[".env\nCLOUDFLARE_TUNNEL_TOKEN=..."]
end
    
 
   conf_file -->|mounted into| mosq_volume
 
   env_file -->|provides| cf_env

Mosquitto Service Configuration

The mosquitto service docker-compose.yml:4-9 runs the Eclipse Mosquitto broker with the following characteristics:

The broker exposes two listeners defined in mosquitto.conf:1-6:

Anonymous access is enabled mosquitto.conf2 meaning no authentication is required to connect to the broker. For details on anonymous access implications, see Anonymous Access.

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

Cloudflared Service Configuration

The cloudflared service docker-compose.yml:11-17 establishes a secure tunnel to Cloudflare's network with the following configuration:

The --no-autoupdate flag prevents automatic updates of the cloudflared binary, ensuring consistent behavior. The token authenticates the tunnel client with Cloudflare's network.

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

sequenceDiagram
    participant Client as "MQTT Client"
    participant CFEdge as "Cloudflare Edge Network"
    participant cloudflared as "cloudflared container"
    participant mosquitto as "mosquitto container"
    
    Note over cloudflared: Startup with CLOUDFLARE_TUNNEL_TOKEN
    cloudflared->>CFEdge: Establish encrypted tunnel
    CFEdge-->>cloudflared: Tunnel connected
    
    Note over Client,mosquitto: Client Connection Flow
    Client->>CFEdge: MQTT CONNECT to public hostname
    CFEdge->>cloudflared: Route through tunnel
    cloudflared->>mosquitto: Proxy to mosquitto:9001
    mosquitto-->>cloudflared: MQTT CONNACK
    cloudflared-->>CFEdge: Through tunnel
    CFEdge-->>Client: MQTT CONNACK
    
    Note over Client,mosquitto: Message Publishing
    Client->>CFEdge: MQTT PUBLISH topic/message
    CFEdge->>cloudflared: Through tunnel
    cloudflared->>mosquitto: Proxy to port 9001
    mosquitto->>mosquitto: Deliver to subscribers
    mosquitto-->>Client: MQTT PUBACK (via tunnel)

Traffic Flow

The following diagram illustrates how MQTT traffic flows from external clients to the internal broker through Cloudflare's tunnel.

Traffic Flow Steps

  1. Tunnel Establishment : The cloudflared container connects outbound to Cloudflare using the CLOUDFLARE_TUNNEL_TOKEN from .env docker-compose.yml:16-17
  2. Client Connection : External MQTT clients connect to the Cloudflare public hostname configured in the Zero Trust dashboard README.md:59-64
  3. Proxy Routing : Cloudflare routes the traffic through the encrypted tunnel to the cloudflared container
  4. Internal Forwarding : The cloudflared container proxies traffic to mosquitto:9001 README.md62
  5. MQTT Processing : The mosquitto container processes MQTT messages on its WebSocket listener mosquitto.conf:4-5

Note that the cloudflared service proxies to mosquitto:9001 specifically, which corresponds to the WebSocket listener mosquitto.conf:4-5 rather than the standard TCP listener on port 1883 mosquitto.conf1

Sources : README.md:15-73 docker-compose.yml:13-14 mosquitto.conf:1-6

Project Structure

The repository contains the following key files:

File Descriptions

File PathPurposeNotes
README.mdSetup guide and documentationIncludes Cloudflare dashboard walkthrough
docker-compose.ymlService definitions for mosquitto and cloudflaredOrchestrates both containers
mosquitto.confMosquitto broker configurationDefines listeners on ports 1883 and 9001
.envEnvironment variables (contains CLOUDFLARE_TUNNEL_TOKEN)Not version controlled (secret)
.env.sampleTemplate for .env fileShows required variables without actual values
.gitignoreGit exclusionsPrevents committing .env and data/*
.github/workflows/ci.ymlGitHub Actions CI pipelineTests mosquitto startup

Sources : README.md:1-73 docker-compose.yml:1-18 mosquitto.conf:1-6

How the System Works

Network Topology

The system eliminates the need for port forwarding or inbound firewall rules because the cloudflared container establishes an outbound connection to Cloudflare's network docker-compose.yml14 This architectural pattern is known as a reverse tunnel.

Sources : README.md:15-20 docker-compose.yml:11-17

Key Characteristics

  1. No Direct Internet Exposure : The mosquitto broker is never directly accessible from the internet
  2. Outbound-Only Connection : The cloudflared container initiates the tunnel connection to Cloudflare
  3. Service Discovery : Docker's internal DNS resolves mosquitto to the mosquitto container docker-compose.yml6
  4. WebSocket Routing : Public traffic is routed to the WebSocket listener on port 9001 mosquitto.conf:4-5
  5. Anonymous Access : No authentication is required at the MQTT protocol level mosquitto.conf2

For detailed architectural information, see System Architecture. For security considerations, see Security Model.

Sources : docker-compose.yml:1-18 mosquitto.conf:1-6 README.md:15-73

Quick Start Reference

To deploy this system, you need to:

  1. Create a Cloudflare Tunnel and obtain the CLOUDFLARE_TUNNEL_TOKEN README.md:23-54
  2. Create a .env file with the token README.md51
  3. Configure a public hostname to route to mosquitto:9001 README.md62
  4. Run docker compose up README.md:70-72

For detailed step-by-step instructions, see Getting Started. For Cloudflare-specific configuration, see Cloudflare Tunnel Configuration.

Sources : README.md:23-73

Branch Variants

This repository includes a variant branch called protected-no-wildcard that adds:

  • Topic access control via ACL file (restricts wildcard searches)
  • Encrypted retained messages using gocryptfs
  • Auto-save functionality for retained messages

For information about these advanced features, see Topic Access Control (ACL)) and Encrypted Retained Messages.

Sources : README.md:5-11

Next Steps

Sources : README.md:1-73


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

Relevant source files

Purpose and Scope

This page provides a step-by-step guide for deploying the Docker MQTT Mosquitto Cloudflare Tunnel system for the first time. It covers the complete workflow from initial Cloudflare configuration through container deployment and verification.

For detailed information about specific aspects of the setup:

Setup Overview

The system deployment consists of four primary phases:

  1. Cloudflare Tunnel Creation : Create and configure a tunnel in the Cloudflare Zero Trust dashboard
  2. Token Extraction : Obtain the CLOUDFLARE_TUNNEL_TOKEN from Cloudflare
  3. Local Configuration : Create a .env file with the tunnel token
  4. Container Deployment : Start the mosquitto and cloudflared services using Docker Compose

Setup Workflow

Diagram: Complete Setup Process

Sources : README.md:23-73

Configuration File Relationships

Diagram: Configuration Files and Service Dependencies

Sources : docker-compose.yml:1-18 .env.sample:1-3 README.md:50-52

Quick Start Steps

Step 1: Cloudflare Tunnel Setup

Navigate to the Cloudflare Zero Trust dashboard and create a new tunnel:

  1. Access Zero TrustNetworksTunnels
  2. Click Create a tunnel
  3. Select Cloudflared as the tunnel type
  4. Provide a tunnel name (e.g., mqtt-tunnel)
  5. On the connector installation screen, select Docker as the environment
  6. Copy the token from the provided Docker command

Important : Do not execute the Docker command shown in the Cloudflare dashboard. This repository provides its own Docker Compose configuration that supersedes that command.

Sources : README.md:27-54

Step 2: Public Hostname Configuration

Configure the public hostname that will route traffic to the MQTT broker:

Configuration FieldValueDescription
Public hostnameyour-subdomain.yourdomain.comThe public URL clients will connect to
Service TypeHTTPProtocol type for the tunnel
Service URLmosquitto:9001Internal Docker service reference

The service URL mosquitto:9001 uses Docker's internal DNS resolution to route traffic to the mosquitto container defined in docker-compose.yml:4-9 on port 9001 (WebSocket listener).

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

Step 3: Environment Configuration

Create a .env file in the repository root directory:

Edit .env and set the CLOUDFLARE_TUNNEL_TOKEN variable with the token obtained from Step 1:

CLOUDFLARE_TUNNEL_TOKEN=your_actual_token_here

The .env file is excluded from version control to prevent accidental token exposure.

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

Step 4: Container Deployment

Deploy both services using Docker Compose:

This command:

  1. Pulls the eclipse-mosquitto:latest image (if not cached)
  2. Pulls the cloudflare/cloudflared:latest image (if not cached)
  3. Starts the mosquitto service with mounted configuration from docker-compose.yml:7-8
  4. Starts the cloudflared service with the tunnel token from docker-compose.yml:16-17

Add the -d flag to run in detached mode:

Sources : README.md:68-72 docker-compose.yml:1-18

Verification

After deployment, verify both containers are running:

Expected output should show two containers:

CONTAINER IDIMAGECOMMANDSTATUSPORTSNAMES
<id>eclipse-mosquitto:latest/docker-entrypoint...Up X seconds1883/tcp, 9001/tcpmosquitto
<id>cloudflare/cloudflared:latesttunnel --no-autoupdate...Up X secondscloudflared

Container Logs

Check the cloudflared container logs to verify tunnel establishment:

Successful output includes lines indicating the tunnel has been registered and connected to Cloudflare's network.

Check the mosquitto container logs to verify broker initialization:

Expected output shows listener initialization on ports 1883 and 9001.

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

Service Architecture During Startup

Diagram: Container Initialization Sequence

Sources : docker-compose.yml:1-18 README.md:68-72

Post-Deployment

Once both containers are running and the tunnel is established:

  1. External MQTT clients can connect to the public hostname configured in Step 2
  2. Traffic routes through Cloudflare's network to the cloudflared container
  3. The cloudflared container proxies traffic to mosquitto:9001
  4. The mosquitto broker handles MQTT connections on the WebSocket listener

Initial Connection Test

Test the public endpoint using an MQTT client. Cloudflare Tunnel typically uses port 443 (HTTPS) for the public endpoint, which is proxied to the internal mosquitto:9001 WebSocket listener.

Sources : README.md:15-20

Stopping the System

To stop both containers:

This command stops and removes both containers. The mosquitto.conf configuration file and .env file remain intact for the next startup. The docker-compose.yml9 restart: unless-stopped policy ensures containers restart automatically unless explicitly stopped.

Sources : docker-compose.yml:1-18

Next Steps

For detailed configuration and advanced topics:

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


GitHub

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

The source code for this project is available on GitHub.

Prerequisites

Relevant source files

This document lists and explains all prerequisites required before deploying the Docker MQTT Mosquitto Cloudflare Tunnel system. These requirements must be satisfied before proceeding to tunnel configuration, mosquitto configuration, and deployment steps.

For step-by-step setup instructions after meeting these prerequisites, see Cloudflare Tunnel Configuration. For system architecture context, see System Architecture.


Purpose and Scope

This page covers the software, accounts, and knowledge required to successfully deploy and operate the system. Each prerequisite is explained with its purpose, minimum version requirements where applicable, and relationship to specific system components.


Required Software Components

The system requires several software components to be installed on the host machine where containers will run.

Docker Engine

Purpose : Docker Engine provides the container runtime environment for both the mosquitto and cloudflared services defined in docker-compose.yml:1-18

Minimum Version : Docker Engine 20.10.0 or later (supports Compose file version 3.8)

Verification :

Used By :

Sources : docker-compose.yml, README.md


Docker Compose

Purpose : Docker Compose orchestrates the multi-container application, managing service startup, networking, and configuration as defined in docker-compose.yml1

Minimum Version : Docker Compose 1.29.0 or later (supports version 3.8 specification)

Verification :

Note : Modern Docker installations include Compose V2 as a Docker CLI plugin (docker compose) rather than a standalone binary (docker-compose).

Used By :

Sources : docker-compose.yml, README.md


Git

Purpose : Git is required to clone the repository and maintain version control. The repository structure relies on .gitignore rules to protect secrets.

Minimum Version : Git 2.0 or later

Verification :

Used By :

  • Cloning repository: git clone https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel
  • Version control practices documented in Version Control Practices

Sources : README.md


Software Prerequisites Mapping

The following diagram maps software prerequisites to the specific container services and configuration files they support:

Sources : docker-compose.yml, README.md

graph TB
    subgraph "Host System Prerequisites"
        DockerEngine["Docker Engine\n(20.10.0+)"]
DockerCompose["Docker Compose\n(1.29.0+)"]
Git["Git\n(2.0+)"]
end
    
    subgraph "Repository Files"
        ComposeYML["docker-compose.yml\nversion: '3.8'"]
MosqConf["mosquitto.conf"]
EnvSample[".env.sample"]
GitIgnore[".gitignore"]
end
    
    subgraph "Container Services"
        MosqContainer["mosquitto container\neclipse-mosquitto:latest"]
CFContainer["cloudflared container\ncloudflare/cloudflared:latest"]
end
    
 
   Git -->|clones| ComposeYML
 
   Git -->|clones| MosqConf
 
   Git -->|clones| EnvSample
 
   Git -->|respects| GitIgnore
    
 
   DockerCompose -->|parses| ComposeYML
 
   DockerCompose -->|orchestrates| MosqContainer
 
   DockerCompose -->|orchestrates| CFContainer
    
 
   DockerEngine -->|runs| MosqContainer
 
   DockerEngine -->|runs| CFContainer
    
 
   ComposeYML -->|defines service| MosqContainer
 
   ComposeYML -->|defines service| CFContainer

Required Accounts and Access

Cloudflare Account

Purpose : A Cloudflare account with Zero Trust access is required to create tunnels and obtain the CLOUDFLARE_TUNNEL_TOKEN used by the cloudflared service.

Registration : Free account available at https://dash.cloudflare.com/sign-up

Required Access :

  • Cloudflare Zero Trust dashboard access
  • Ability to create and configure tunnels
  • Ability to configure public hostnames

Used For :

Sources : README.md


Zero Trust Portal Access

Purpose : The Zero Trust portal is where Cloudflare Tunnels are created and managed.

Access Path : Cloudflare Dashboard → Zero Trust → Networks → Tunnels

Required Permissions :

  • Create new tunnels
  • Configure tunnel connectors
  • Set up public hostnames

Navigation Steps (detailed in Cloudflare Tunnel Configuration):

  1. Zero Trust dashboard access README.md:27-29
  2. Tunnel creation README.md:34-45
  3. Public hostname configuration README.md:55-66

Sources : README.md


graph LR
    subgraph "External Services"
        CFDashboard["Cloudflare Dashboard\ndash.cloudflare.com"]
ZeroTrust["Zero Trust Portal\nTunnel Management"]
end
    
    subgraph "Local Environment"
        Developer["Developer/Operator"]
EnvFile[".env file\nCLOUDFLARE_TUNNEL_TOKEN=..."]
end
    
    subgraph "Docker Compose Configuration"
        ComposeFile["docker-compose.yml\nline 14: command with token\nline 17: environment var"]
end
    
    subgraph "Running Container"
        CFDContainer["cloudflared container\ntunnel run --token"]
end
    
 
   Developer -->|logs into| CFDashboard
 
   CFDashboard -->|navigates to| ZeroTrust
 
   ZeroTrust -->|creates tunnel, generates| Developer
 
   Developer -->|creates and populates| EnvFile
 
   EnvFile -->|provides ${CLOUDFLARE_TUNNEL_TOKEN}| ComposeFile
 
   ComposeFile -->|passes to| CFDContainer
 
   CFDContainer -->|authenticates with| ZeroTrust

Account and Token Flow

This diagram shows how Cloudflare account credentials and tunnel tokens flow from external services to the deployed containers:

Sources : docker-compose.yml, README.md


Knowledge Prerequisites

While not strictly required for deployment, familiarity with the following concepts will aid in understanding, configuring, and troubleshooting the system:

Knowledge AreaRelevanceRelated Components
MQTT ProtocolUnderstanding publish/subscribe messaging, topics, QoS levelsmosquitto service, MQTT clients
Docker FundamentalsContainer concepts, image management, volume mountsAll services in docker-compose.yml
Docker ComposeService orchestration, networking, environment variablesdocker-compose.yml structure
Networking BasicsPorts, protocols (TCP/WebSocket), DNSListener configuration, tunnel routing
Cloudflare TunnelsSecure tunnel concepts, Zero Trust principlescloudflared service operation
Linux Command LineBasic shell commands, file permissions, process managementDeployment and troubleshooting

Sources : README.md, docker-compose.yml


System Requirements

Hardware Requirements

The system has minimal hardware requirements suitable for most modern systems:

ResourceMinimumRecommendedPurpose
CPU1 core2+ coresContainer orchestration, MQTT message processing
RAM512 MB1 GB+mosquitto and cloudflared containers
Disk Space500 MB2 GB+Docker images, configuration files, optional data directory
NetworkInternet connectionStable broadbandCloudflare tunnel connectivity

Sources : docker-compose.yml


Operating System Compatibility

The system is platform-agnostic due to containerization. Docker Engine must be supported by the host OS:

Operating SystemSupport StatusNotes
LinuxFully SupportedNative Docker support, recommended for production
macOSFully SupportedRequires Docker Desktop
WindowsFully SupportedRequires Docker Desktop with WSL2 backend

Sources : docker-compose.yml


Network Requirements

RequirementDescriptionUsed By
Outbound HTTPS (443)Required for cloudflared to establish tunneldocker-compose.yml:11-17
No Inbound PortsSystem does not require any inbound firewall rulesSecurity model documented in Security Model
DNS ResolutionRequired to resolve Cloudflare endpointscloudflared service
Internal Docker NetworkDefault bridge network for inter-container communicationCommunication between cloudflared and mosquitto:9001

Sources : docker-compose.yml, README.md


Prerequisites Verification Checklist

Before proceeding to Cloudflare Tunnel Configuration, verify all prerequisites are met:

Sources : docker-compose.yml, README.md


File Structure Prerequisites

After cloning the repository, verify the following files are present:

File PathPurposeRequired For
docker-compose.ymlService orchestration configurationAll deployment operations
mosquitto.confMosquitto broker configurationmosquitto service startup
.env.sampleTemplate for environment variablesCreating .env file (see Environment Variables)
.gitignoreExcludes secrets from version controlProtecting CLOUDFLARE_TUNNEL_TOKEN
README.mdSetup documentationReference during deployment

Note : The .env file does not exist in the repository and must be created manually from .env.sample after obtaining the Cloudflare tunnel token. This is documented in Environment Variables.

Sources : docker-compose.yml, README.md


Next Steps

After verifying all prerequisites are met:

  1. Proceed to Cloudflare Tunnel Configuration to create a tunnel and obtain your CLOUDFLARE_TUNNEL_TOKEN
  2. Then configure environment variables as documented in Environment Variables
  3. Review Mosquitto Configuration to understand broker settings
  4. Finally, deploy the system following Deployment instructions

For advanced configuration options available in other branches, see Topic Access Control (ACL)) and Encrypted Retained Messages.

Sources : README.md, docker-compose.yml


GitHub

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

The source code for this project is available on GitHub.

System Architecture

Relevant source files

Purpose and Scope

This document describes the technical architecture of the Docker MQTT Mosquitto Cloudflare Tunnel system, including its component structure, network topology, data flow patterns, and configuration management. This page focuses on the structural aspects of the system. For security-specific architecture details, see Security Model.

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

System Components Overview

The system consists of two primary Docker containers orchestrated by Docker Compose, with supporting configuration files that define their behavior.

ComponentTypeImagePurpose
mosquittoService Containereclipse-mosquitto:latestMQTT broker providing message routing and pub/sub functionality
cloudflaredService Containercloudflare/cloudflared:latestCloudflare Tunnel client establishing secure outbound connection
mosquitto.confConfiguration FileN/ADefines MQTT listener ports, protocols, and access policies
docker-compose.ymlOrchestrationN/ADefines service configurations, networking, and restart policies
.envSecrets FileN/AContains CLOUDFLARE_TUNNEL_TOKEN for tunnel authentication

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

Container Architecture

The following diagram illustrates the two-container architecture and their configuration dependencies:

graph TB
    subgraph DockerHost["Docker Host"]
subgraph ComposeStack["Docker Compose Stack"]
MosquittoContainer["mosquitto container"]
CloudflaredContainer["cloudflared container"]
end
        
        subgraph ConfigFiles["Configuration Files"]
ComposeYML["docker-compose.yml"]
MosquittoConf["mosquitto.conf"]
EnvFile[".env"]
end
    end
    
    subgraph ExternalDeps["External Dependencies"]
MosquittoImage["eclipse-mosquitto:latest"]
CloudflaredImage["cloudflare/cloudflared:latest"]
CloudflareNetwork["Cloudflare Edge Network"]
end
    
 
   ComposeYML -->|orchestrates| MosquittoContainer
 
   ComposeYML -->|orchestrates| CloudflaredContainer
    
 
   MosquittoImage -->|provides runtime| MosquittoContainer
 
   CloudflaredImage -->|provides runtime| CloudflaredContainer
    
 
   MosquittoConf -->|volume mount ./mosquitto.conf:/mosquitto/config/mosquitto.conf| MosquittoContainer
 
   EnvFile -->|environment variable CLOUDFLARE_TUNNEL_TOKEN| CloudflaredContainer
    
 
   CloudflaredContainer -->|tunnel --no-autoupdate run --token| CloudflareNetwork
    
 
   MosquittoContainer -.->|internal proxy mosquitto:9001| CloudflaredContainer

Container Service Architecture

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

Service Configuration Details

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

  • Container Name: mosquitto (used for internal DNS resolution)
  • Image: eclipse-mosquitto:latest
  • Volume Mount: ./mosquitto.conf:/mosquitto/config/mosquitto.conf (read-only configuration)
  • Restart Policy: unless-stopped (automatic recovery from failures)

The cloudflared service is defined in docker-compose.yml:11-17 with the following characteristics:

  • Container Name: cloudflared
  • Image: cloudflare/cloudflared:latest
  • Command: tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}
  • Environment Variable: CLOUDFLARE_TUNNEL_TOKEN (sourced from .env file)
  • Restart Policy: unless-stopped

Sources: docker-compose.yml:1-18

Network Topology

The system implements a multi-layer network architecture with both internal Docker networking and external Cloudflare tunnel connectivity.

graph TB
    subgraph Internet["Public Internet"]
MQTTClients["MQTT Clients"]
end
    
    subgraph CloudflareInfra["Cloudflare Infrastructure"]
CFEdge["Cloudflare Edge Network"]
TunnelService["Tunnel Service"]
end
    
    subgraph DockerInternal["Docker Internal Network (default)"]
subgraph CloudflaredContainer["cloudflared container"]
TunnelClient["tunnel client process"]
end
        
        subgraph MosquittoContainer["mosquitto container"]
Listener1883["listener 1883\nTCP MQTT"]
Listener9001["listener 9001\nWebSocket MQTT"]
end
    end
    
 
   MQTTClients -->|MQTT/WebSocket public hostname| CFEdge
 
   CFEdge <-->|encrypted tunnel outbound from cloudflared| TunnelService
 
   TunnelService <-->|tunnel connection| TunnelClient
 
   TunnelClient -->|HTTP proxy to mosquitto:9001| Listener9001
    
 
   Listener1883 -.->|not exposed externally local only| DockerInternal
 
   Listener9001 -->|internal routing| TunnelClient

Network Layer Architecture

Sources: mosquitto.conf:1-6 docker-compose.yml:11-17 README.md:56-65

Port and Protocol Configuration

The mosquitto service exposes two listener ports configured in mosquitto.conf:1-6:

Listener PortProtocolConfigurationPurposeExternal Access
1883TCP MQTTlistener 1883
allow_anonymous trueStandard MQTT protocolNot exposed (container-internal only)
9001WebSocket MQTTlistener 9001
protocol websocketsMQTT over WebSockets (inherits allow_anonymous true)Proxied via Cloudflare Tunnel

The cloudflared service does not expose any ports directly. It establishes an outbound connection to Cloudflare's network and proxies inbound traffic to mosquitto:9001 via Docker's internal DNS resolution.

Sources: mosquitto.conf:1-6 README.md:62

Data Flow Architecture

The following diagram illustrates the complete message flow from external clients through the Cloudflare Tunnel to the Mosquitto broker.

sequenceDiagram
    participant Client as "MQTT Client"
    participant CFEdge as "Cloudflare Edge"
    participant Cloudflared as "cloudflared container"
    participant Mosquitto as "mosquitto container"
    participant MosqConf as "mosquitto.conf"
    
    Note over Cloudflared: Startup initialization
    Cloudflared->>Cloudflared: Read CLOUDFLARE_TUNNEL_TOKEN from environment
    Cloudflared->>CFEdge: Execute: tunnel --no-autoupdate run --token
    CFEdge-->>Cloudflared: Tunnel established (outbound connection)
    
    Note over Mosquitto: Startup initialization
    Mosquitto->>MosqConf: Load /mosquitto/config/mosquitto.conf
    MosqConf-->>Mosquitto: Configure listener 1883 (TCP)
    MosqConf-->>Mosquitto: Configure listener 9001 (WebSocket)
    MosqConf-->>Mosquitto: Set allow_anonymous true
    
    Note over Client,Mosquitto: MQTT Connection Phase
    Client->>CFEdge: MQTT CONNECT via public hostname
    CFEdge->>Cloudflared: Route through tunnel
    Cloudflared->>Mosquitto: HTTP proxy to mosquitto:9001
    Mosquitto->>Mosquitto: Validate listener 9001 (WebSocket)
    Mosquitto->>Mosquitto: Check allow_anonymous (true)
    Mosquitto-->>Cloudflared: MQTT CONNACK
    Cloudflared-->>CFEdge: Tunnel response
    CFEdge-->>Client: MQTT CONNACK
    
    Note over Client,Mosquitto: MQTT Publish Phase
    Client->>CFEdge: MQTT PUBLISH topic/message
    CFEdge->>Cloudflared: Tunnel
    Cloudflared->>Mosquitto: Proxy to :9001
    Mosquitto->>Mosquitto: Route to topic subscribers
    Mosquitto-->>Cloudflared: MQTT PUBACK
    Cloudflared-->>CFEdge: Tunnel
    CFEdge-->>Client: MQTT PUBACK

MQTT Message Flow Sequence

Sources: docker-compose.yml:11-17 mosquitto.conf:1-6 README.md:15-73

Internal Service Communication

Within the Docker Compose stack, services communicate using Docker's built-in DNS resolution. The cloudflared container references the mosquitto service by its container name (mosquitto) as configured in docker-compose.yml6

When configuring the Cloudflare Tunnel's public hostname, the service URL is set to mosquitto:9001 (as described in README.md:62), which resolves to:

  1. Hostname: mosquitto → Docker DNS resolves to mosquitto container's internal IP
  2. Port: 9001 → The WebSocket listener defined in mosquitto.conf:4-5

No explicit Docker network configuration is required because both services use the default bridge network created by Docker Compose.

Sources: docker-compose.yml6 mosquitto.conf:4-5 README.md:62

Configuration File Relationships

The following diagram maps configuration files to their consuming containers and the specific configuration aspects they control:

graph LR
    subgraph ConfigLayer["Configuration Layer"]
ComposeFile["docker-compose.yml"]
MosqConf["mosquitto.conf"]
EnvFile[".env"]
EnvSample[".env.sample"]
end
    
    subgraph RuntimeLayer["Runtime Layer"]
MosqContainer["mosquitto container"]
CFContainer["cloudflared container"]
DockerEngine["Docker Engine"]
end
    
    subgraph ConfigContent["Configuration Content"]
ServiceDef["Service definitions:\nimage, container_name,\nrestart policies"]
VolumeMount["Volume mount:\n./mosquitto.conf:/mosquitto/config/mosquitto.conf"]
EnvVar["Environment variable:\nCLOUDFLARE_TUNNEL_TOKEN"]
ListenerConf["Listeners:\n1883 (TCP)\n9001 (WebSocket)"]
AnonAccess["Access control:\nallow_anonymous true"]
TunnelToken["Tunnel token:\nCLOUDFLARE_TUNNEL_TOKEN=..."]
end
    
 
   EnvSample -.->|template for| EnvFile
    
 
   ComposeFile --> ServiceDef
 
   ComposeFile --> VolumeMount
 
   ComposeFile --> EnvVar
    
 
   ServiceDef -->|defines| MosqContainer
 
   ServiceDef -->|defines| CFContainer
    
 
   VolumeMount -->|mounts into| MosqContainer
 
   EnvVar -->|injects into| CFContainer
    
 
   MosqConf --> ListenerConf
 
   MosqConf --> AnonAccess
 
   ListenerConf -->|configures| MosqContainer
 
   AnonAccess -->|configures| MosqContainer
    
 
   EnvFile --> TunnelToken
 
   TunnelToken -->|provides to| CFContainer
    
 
   DockerEngine -->|runs| MosqContainer
 
   DockerEngine -->|runs| CFContainer

Configuration Dependency Graph

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

Configuration File Purposes

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

The .env file is excluded from version control (see .gitignore) to prevent accidental exposure of the CLOUDFLARE_TUNNEL_TOKEN, which provides authentication credentials for the Cloudflare Tunnel.

Sources: docker-compose.yml:1-18 mosquitto.conf:1-6 README.md:51

Architectural Patterns

Reverse Proxy via Outbound Tunnel

The system implements a reverse proxy pattern where the cloudflared container acts as a secure tunnel endpoint that proxies external traffic to the internal mosquitto service. This pattern has several architectural implications:

  1. No Inbound Ports Required: The cloudflared container initiates an outbound connection to Cloudflare's network (see docker-compose.yml14), eliminating the need for inbound firewall rules
  2. Internal Service Discovery: The proxy uses Docker's internal DNS to resolve mosquitto:9001 to the appropriate container
  3. Protocol Translation: External MQTT clients connect via standard protocols, while the tunnel uses HTTP/HTTPS for transport

Sources: docker-compose.yml:11-17 README.md:15-73

Stateless Container Design

Both containers follow a stateless design pattern :

  • mosquitto: No persistent data volume is configured in docker-compose.yml:4-9 meaning broker state (retained messages, subscriptions) is ephemeral and lost on container restart
  • cloudflared: The tunnel client is stateless; all configuration is provided via the CLOUDFLARE_TUNNEL_TOKEN environment variable

This design prioritizes simplicity and ease of deployment over data persistence. For persistent storage requirements, see Advanced Topics.

Sources: docker-compose.yml:1-18

Service Restart Policy

Both services use restart: unless-stopped (docker-compose.yml:9-15), which provides automatic recovery from failures while allowing manual stops to persist. This policy ensures:

  • Containers automatically restart after crashes or Docker daemon restarts
  • Manually stopped containers remain stopped (via docker compose stop)
  • System reboots trigger container restarts

Sources: docker-compose.yml:9-15

Component Lifecycle

The following table describes the startup sequence and dependencies:

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

There is no explicit startup ordering defined in docker-compose.yml (no depends_on directive), meaning both containers start simultaneously. However, the cloudflared container will retry tunnel connections until successful, providing implicit resilience to timing issues.

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


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 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


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

Relevant source files

Purpose and Scope

This document describes the security architecture of the Docker MQTT Mosquitto Cloudflare Tunnel system, including network security, transport encryption, authentication mechanisms, and secret management practices. It explains the security trade-offs in the default configuration and identifies trust boundaries.

For detailed configuration of access control lists and encryption features, see Topic Access Control (ACL)) and Encrypted Retained Messages. For production deployment hardening, see Production Considerations.


Security Architecture Overview

The system implements a multi-layered security model centered on Cloudflare Tunnel's outbound-only connection architecture. The primary security mechanism is network isolation combined with transport encryption, while the MQTT broker itself operates in an anonymous-access mode suitable for trusted environments.

graph TB
    Internet["Internet\n(Untrusted)"]
CFEdge["Cloudflare Edge\nDDoS Protection, WAF"]
CFTunnel["Cloudflare Tunnel\nTLS 1.3 Encrypted"]
Cloudflared["cloudflared container\nToken-authenticated"]
DockerNet["Docker Internal Network\n(bridge mode)"]
Mosquitto["mosquitto container\nAnonymous access"]
Internet --> CFEdge
 
   CFEdge --> CFTunnel
 
   CFTunnel --> Cloudflared
 
   Cloudflared --> DockerNet
 
   DockerNet --> Mosquitto
    
    subgraph "External Security Boundary"
        CFEdge
        CFTunnel
    end
    
    subgraph "Host Security Boundary"
        Cloudflared
        DockerNet
        Mosquitto
    end

Security Layers Diagram

Sources: README.md:15-20, mosquitto.conf:1-6


Network Security Architecture

Outbound-Only Connection Model

The system eliminates the need for inbound firewall rules by establishing an outbound-only tunnel connection from cloudflared to Cloudflare's network. This architecture prevents direct internet exposure of the MQTT broker.

Key Properties:

sequenceDiagram
    participant Host as "Docker Host"
    participant CFD as "cloudflared container"
    participant CF as "Cloudflare Network"
    participant FW as "Host Firewall"
    
    Note over CFD: Container starts with\nCLOUDFLARE_TUNNEL_TOKEN
    CFD->>CF: Outbound HTTPS connection\nAuthenticate with token
    CF-->>CFD: Tunnel established\nTLS 1.3 encrypted
    
    Note over FW: No inbound rules required\nNote over Host,CF: All traffic flows through\nestablished tunnel
    
    CF->>CFD: Route MQTT client traffic
    CFD->>CFD: Proxy to mosquitto:9001
Security AspectImplementationFile Reference
Inbound PortsNone required on hostN/A
Outbound ConnectionHTTPS to Cloudflare edgeN/A
Container NetworkDocker bridge (default)docker-compose.yml
Service ResolutionDNS-based via container namedocker-compose.yml

Sources: README.md:15-20, README.md:55-66

Docker Network Isolation

The mosquitto and cloudflared containers communicate through Docker's default bridge network. The mosquitto container is not exposed to the host network or internet directly.

graph LR
    subgraph "Docker Bridge Network"
        CF["cloudflared\ncontainer_name: cloudflared"]
MQ["mosquitto\ncontainer_name: mosquitto"]
end
    
    subgraph "Service Resolution"
        DNS["Docker DNS\nresolves 'mosquitto'\nto container IP"]
end
    
 
   CF -->|Internal proxy to mosquitto:9001| DNS
 
   DNS --> MQ
    
    Note1["No host port mapping\nfor mosquitto:9001"]

The hostname mosquitto in the public hostname configuration README.md62 is resolved by Docker's internal DNS to the IP address of the mosquitto container. This ensures traffic never traverses the host network.

Sources: README.md:62-64


Transport Security

Cloudflare Tunnel Encryption

All traffic between MQTT clients and the cloudflared container is encrypted using Cloudflare Tunnel's TLS 1.3 transport:

Encryption Scope:

graph LR
    Client["MQTT Client"]
CFEdge["Cloudflare Edge"]
Tunnel["Encrypted Tunnel\nTLS 1.3"]
CFD["cloudflared"]
Plain["Plaintext Proxy"]
MQ["mosquitto:9001"]
Client -->|MQTT over WebSocket/HTTP| CFEdge
 
   CFEdge -->|Encrypted| Tunnel
 
   Tunnel -->|Encrypted| CFD
 
   CFD -->|Plaintext internal| Plain
 
   Plain --> MQ
  • Encrypted: Internet → Cloudflare Edge → cloudflared container
  • Plaintext: cloudflared container → mosquitto container (internal Docker network)

The plaintext segment is considered secure because it operates within the Docker bridge network, isolated from external access.

Sources: README.md:15-20

MQTT Protocol Listeners

The mosquitto service configures two listeners with different security characteristics:

ListenerPortProtocolTransport SecurityConfiguration
TCP1883MQTTNone (internal only)mosquitto.conf1
WebSocket9001MQTT over WSVia Cloudflare Tunnelmosquitto.conf:4-5

Port 9001 is the only listener routed through the Cloudflare Tunnel README.md62 Port 1883 is accessible only within the Docker network.

Sources: mosquitto.conf:1-5, README.md:62


Authentication and Authorization

Anonymous Access Model

The system is configured with anonymous access enabled, meaning no authentication is required at the MQTT broker level:

listener 1883
allow_anonymous true

listener 9001
protocol websockets

Configuration: mosquitto.conf2

This configuration has the following security implications:

Trust Model:

The anonymous access model operates under the assumption that:

  1. Cloudflare Tunnel provides network-level access control - Only authorized clients can reach the broker through the configured public hostname
  2. All traffic reaching the broker is trusted - The broker does not differentiate between clients
  3. Internal Docker network is trusted - No authentication within the container network

Sources: mosquitto.conf:1-6

Access Control Limitations

The default configuration provides no MQTT-level access control :

Security ControlStatusImplication
Username/PasswordDisabledAny client can connect
ACL (Topic Permissions)NoneAll clients can access all topics
Client ID RestrictionsNoneAny client ID accepted
Connection LimitsNone (default)No rate limiting at MQTT layer

For environments requiring MQTT-level access control, see Topic Access Control (ACL)).

Sources: mosquitto.conf:1-6


Secret Management

Cloudflare Tunnel Token

The CLOUDFLARE_TUNNEL_TOKEN is the primary secret in the system, used to authenticate the cloudflared container with Cloudflare's network.

graph TB
    Dashboard["Cloudflare Zero Trust Dashboard"]
Token["CLOUDFLARE_TUNNEL_TOKEN\n(secret value)"]
EnvSample[".env.sample\n(template)"]
EnvFile[".env\n(actual secret)"]
GitIgnore[".gitignore"]
CFD["cloudflared container\nenvironment variable"]
Dashboard -->|Generate during tunnel creation| Token
 
   Token -->|Developer copies| EnvFile
 
   EnvSample -.->|Template reference no actual token| EnvFile
 
   GitIgnore -->|Excludes from VCS| EnvFile
 
   EnvFile -->|Read at startup| CFD
    
    Note1["Never committed\nto version control"]

Secret Lifecycle Flow

Sources: README.md:47-51, .env.sample:1, .gitignore:1

Version Control Protection

The .gitignore file prevents secrets from entering version control:

.env
data/*

Configuration: .gitignore:1-2

FileVersion ControlledContains SecretsPurpose
.env.sampleYesNoTemplate/documentation
.envNo (ignored)YesActual secrets
docker-compose.ymlYesNoReferences ${CLOUDFLARE_TUNNEL_TOKEN} variable
graph TB
    subgraph "Secure Storage"
        EnvFile[".env file\nFilesystem permissions"]
FS["Host filesystem\nOwner: user\nMode: 0600 recommended"]
end
    
    subgraph "Runtime Access"
        Docker["Docker Engine\nReads .env"]
CFD["cloudflared container\nEnvironment variable"]
end
    
    subgraph "Version Control"
        Git["Git repository"]
GitIgnore[".gitignore\nExcludes .env"]
Remote["Remote repository\nGitHub"]
end
    
 
   EnvFile --> FS
 
   FS --> Docker
 
   Docker --> CFD
    
 
   GitIgnore -.->|Prevents| Git
 
   Git --> Remote
    
    Note1["Token visible in:\n- .env file\n- Docker inspect output\n- Container environment"]

The docker-compose.yml uses variable interpolation to inject the token at runtime without embedding it in version-controlled files.

Sources: .gitignore:1-2, .env.sample:1

Secret Storage Security

Security Considerations:

  1. Filesystem Permissions: The .env file should have restrictive permissions (e.g., chmod 600 .env)
  2. Container Environment: The token is visible via docker inspect cloudflared
  3. Process Environment: The token is visible to processes running as root on the Docker host
  4. Backup Security: Ensure backups of the host system protect the .env file

Sources: .env.sample:1, .gitignore:1


Trust Boundaries

Security Perimeter Mapping

The system defines three trust boundaries:

BoundarySecurity MechanismThreats Mitigated
Internet → CloudflareDDoS protection, WAF, geo-blockingNetwork attacks, malicious traffic
Cloudflare → Docker HostTunnel token authentication, TLS encryptionMan-in-the-middle, unauthorized access
Host → ContainersDocker network isolation, no direct exposureContainer escape attacks
cloudflared → mosquittoNone (trusted internal network)N/A (assumes trusted communication)

Critical Assumption: Traffic that reaches the mosquitto container is assumed trusted because it has passed through the cloudflared proxy, which only accepts traffic from the authenticated Cloudflare Tunnel.

Sources: README.md:15-20, mosquitto.conf:1-6


Security Limitations and Assumptions

Default Configuration Limitations

The system makes the following security trade-offs:

AspectImplementationLimitationRisk Level
MQTT Authenticationallow_anonymous trueNo username/passwordMedium
Topic AuthorizationNo ACL fileAny client can access any topicMedium
Message EncryptionNone at MQTT layerTraffic visible within Docker networkLow
Client IdentificationNo client certificateCannot verify client identityMedium
Rate LimitingCloudflare onlyNo MQTT-level rate limitingLow

Sources: mosquitto.conf:1-6

Appropriate Use Cases

This security model is appropriate for:

  1. Trusted Client Environments - All connecting clients are known and trusted
  2. Single-Tenant Deployments - One user/organization controls all clients
  3. Internal IoT Networks - Devices managed by the same entity
  4. Development/Testing - Non-production environments

Inappropriate Use Cases

This security model is not recommended for:

  1. Multi-Tenant Systems - Multiple independent users/organizations
  2. Public MQTT Brokers - Open access to untrusted clients
  3. Sensitive Data - Financial, health, or personally identifiable information
  4. Compliance Environments - Systems requiring audit trails or access logging

Enhanced Security Options

The repository provides an alternative branch with additional security features:

Protected No-Wildcard Branch

The protected-no-wildcard branch README.md:5-11 implements:

  1. Topic-Based Access Control (ACL)

    • Restricts wildcard subscriptions across users
    • Username-based topic isolation (first topic level = username)
    • ACL file configuration
  2. Encrypted Retained Messages

    • Uses gocryptfs for message encryption at rest
    • Automatic saving after each retained message
    • Encrypted data/ directory

Comparison:

FeatureMain BranchProtected-No-Wildcard Branch
AuthenticationAnonymousRequired (ACL-based)
Topic WildcardsUnrestrictedRestricted per user
Retained MessagesPlaintextEncrypted (gocryptfs)
Access ControlNoneACL file (mosquitto/aclfile)

For implementation details, see Topic Access Control (ACL)) and Encrypted Retained Messages.

Sources: README.md:5-11


Security Best Practices

Deployment Recommendations

Checklist for Production Deployment

Mandatory:

  • Restrict .env file permissions (chmod 600 .env)
  • Review Cloudflare Zero Trust access policies
  • Configure Cloudflare WAF rules if needed
  • Document which clients should have access
  • Plan for token rotation procedures

Recommended:

  • Enable Cloudflare Access policies for additional authentication
  • Implement connection monitoring/alerting
  • Use the protected-no-wildcard branch for multi-user scenarios
  • Configure Docker resource limits
  • Implement backup procedures for retained messages (if used)

Optional:

  • Add TLS termination at Mosquitto level for defense in depth
  • Implement application-layer message encryption
  • Deploy in a dedicated Docker network with network policies
  • Add connection logging and SIEM integration

For production considerations, see Production Considerations.

Sources: README.md:5-11, mosquitto.conf:1-6, .gitignore:1-2


GitHub

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

The source code for this project is available on GitHub.

Mosquitto Configuration

Relevant source files

This document explains the configuration settings in the mosquitto.conf file, which controls the behavior of the Eclipse Mosquitto MQTT broker. This includes listener configuration, port assignments, protocol settings, and access control options.

For information about setting up the Cloudflare Tunnel to expose the broker, see Cloudflare Tunnel Configuration. For information about environment variable configuration, see Environment Variables. For advanced access control configurations available in other branches, see Topic Access Control (ACL)).


Configuration File Overview

The Mosquitto broker is configured through a single configuration file located at mosquitto.conf in the repository root. This file is mounted into the mosquitto Docker container at runtime via a volume mount defined in docker-compose.yml8

File Location and Mounting

PropertyValue
Host Path./mosquitto.conf
Container Path/mosquitto/config/mosquitto.conf
Mount TypeVolume mount (read-only effective)
Defined In docker-compose.yml7-8

The configuration is read by the mosquitto service when the container starts. Changes to the host file require a container restart to take effect.

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


Listener Configuration

The mosquitto.conf file defines two network listeners, each serving different MQTT client connection types. Each listener operates independently and can be configured with different protocols and security settings.

graph TB
    subgraph "mosquitto Container"
        Config["mosquitto.conf"]
Broker["Mosquitto Broker Process"]
subgraph "Listener 1883"
            L1883["listener 1883"]
TCP["Standard MQTT/TCP Protocol"]
Port1883["Port 1883"]
end
        
        subgraph "Listener 9001"
            L9001["listener 9001"]
WS["protocol websockets"]
Port9001["Port 9001"]
end
        
        Anonymous["allow_anonymous true"]
end
    
    subgraph "Cloudflared Routing"
        CFProxy["cloudflared proxy"]
PublicHostname["Public Hostname"]
end
    
 
   Config -->|Configures| L1883
 
   Config -->|Configures| L9001
 
   Config -->|Applies to all listeners| Anonymous
    
 
   L1883 --> TCP
 
   TCP --> Port1883
    
 
   L9001 --> WS
 
   WS --> Port9001
    
 
   PublicHostname -->|Routes to| CFProxy
 
   CFProxy -->|mosquitto:9001| Port9001
    
 
   Broker -->|Binds| Port1883
 
   Broker -->|Binds| Port9001

Listener Architecture

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


Listener 1: Standard MQTT (Port 1883)

The first listener is configured at mosquitto.conf:1-2 and provides standard MQTT protocol support over TCP.

listener 1883
allow_anonymous true
SettingValueDescription
Port1883Standard MQTT port (IANA registered)
ProtocolTCP (implicit)Default MQTT protocol when no protocol directive specified
IP BindingAll interfaces (implicit)No explicit bind address, so listens on 0.0.0.0
AuthenticationAnonymous allowedGoverned by global allow_anonymous true directive

This listener is suitable for:

  • Standard MQTT client libraries
  • IoT devices using native MQTT over TCP
  • Local network connections within Docker's internal network

Note: This listener is not directly exposed through the Cloudflare Tunnel by default. Only the WebSocket listener (port 9001) is routed through Cloudflare as configured in README.md62

Sources: mosquitto.conf:1-2


Listener 2: WebSocket MQTT (Port 9001)

The second listener is configured at mosquitto.conf:4-5 and provides MQTT over WebSockets, enabling browser-based and web application clients.

listener 9001
protocol websockets
SettingValueDescription
Port9001Commonly used WebSocket MQTT port
ProtocolwebsocketsMQTT messages encapsulated in WebSocket frames
IP BindingAll interfaces (implicit)Listens on 0.0.0.0
AuthenticationAnonymous allowedGoverned by global allow_anonymous true directive
Cloudflare RouteYesProxied via cloudflared to public hostname

This listener is the primary entry point for external clients. The Cloudflare Tunnel routes public traffic to mosquitto:9001 as specified in the tunnel configuration described in README.md62

WebSocket Protocol Details

The protocol websockets directive at mosquitto.conf5 instructs Mosquitto to:

  1. Accept WebSocket handshake requests on port 9001
  2. Upgrade HTTP connections to WebSocket protocol
  3. Extract MQTT control packets from WebSocket frames
  4. Encapsulate MQTT responses in WebSocket frames

Sources: mosquitto.conf:4-5 README.md62


Access Control Configuration

Anonymous Access

The configuration file enables anonymous access at mosquitto.conf2:

allow_anonymous true
SettingValueSecurity Impact
Directiveallow_anonymousControls whether unauthenticated connections are permitted
ValuetrueAll clients can connect without credentials
ScopeGlobalApplies to all listeners (1883 and 9001)
ACL EnforcementNoneNo access control list is configured in this file

Security Implications

Important Security Considerations:

  1. No Authentication: Any client that can reach the broker through the Cloudflare Tunnel can connect without providing credentials.

  2. No Authorization: Connected clients have unrestricted access to:

    • Publish to any topic
    • Subscribe to any topic
    • Use wildcard subscriptions (#, +)
  3. Network-Level Security Only: Security relies entirely on:

    • Cloudflare's edge network protections
    • The secrecy of the public hostname
    • Optional Cloudflare Zero Trust policies (not configured by default)
  4. Trust Boundary: The configuration assumes all traffic reaching the mosquitto container through cloudflared is trusted. There is no defense-in-depth at the MQTT layer.

For enhanced security configurations with user authentication and topic-level ACLs, see Topic Access Control (ACL)) which documents the protected-no-wildcard branch implementation.

Sources: mosquitto.conf2 README.md:5-11


Configuration Directives Reference

The table below provides a complete reference for all directives present in mosquitto.conf:1-6:

LineDirectiveValueScopeDescription
1listener1883Listener-specificDefines a network listener on port 1883 with default TCP protocol
2allow_anonymoustrueGlobalPermits connections without username/password authentication
3(blank)--Separator for readability
4listener9001Listener-specificDefines a network listener on port 9001
5protocolwebsocketsListener-specificConfigures the listener at line 4 to use WebSocket protocol
6(blank)--End of file

Sources: mosquitto.conf:1-6


graph TB
    subgraph "Host Filesystem"
        HostFile["./mosquitto.conf\nRepository root"]
HostContent["Line 1: listener 1883\nLine 2: allow_anonymous true\nLine 4: listener 9001\nLine 5: protocol websockets"]
end
    
    subgraph "Docker Volume Mount"
        Mount["Volume Mount Definition\ndocker-compose.yml:7-8"]
end
    
    subgraph "mosquitto Container"
        ContainerFile["/mosquitto/config/mosquitto.conf"]
MosqProcess["mosquitto process\nreads config at startup"]
Listener1["Listener 1883\nTCP"]
Listener2["Listener 9001\nWebSockets"]
end
    
 
   HostFile -->|Contains| HostContent
 
   HostContent -->|Mounted via| Mount
 
   Mount -->|As| ContainerFile
 
   ContainerFile -->|Read by| MosqProcess
 
   MosqProcess -->|Creates| Listener1
 
   MosqProcess -->|Creates| Listener2

Configuration in Docker Context

Volume Mount Mechanism

The mosquitto configuration file is made available to the container through Docker's volume mounting mechanism:

Mount Details:

PropertyValue
Host Source./mosquitto.conf (relative to docker-compose.yml)
Container Destination/mosquitto/config/mosquitto.conf
Mount ModeRead-write (default), but container treats as read-only
Configuration Source docker-compose.yml7-8

Configuration Loading Process

  1. Container Start: Docker Compose starts the mosquitto container per docker-compose.yml:4-9
  2. Volume Mount: Docker mounts ./mosquitto.conf into the container before process execution
  3. Process Initialization: The mosquitto process starts and reads /mosquitto/config/mosquitto.conf
  4. Listener Creation: Mosquitto binds to ports 1883 and 9001 based on listener directives
  5. Ready State: Container enters healthy state, ready to accept connections

Runtime Behavior:

  • Changes to mosquitto.conf on the host filesystem do not automatically apply to the running container
  • The mosquitto process must be restarted to reload configuration: docker compose restart mosquitto
  • During development, use docker compose down and docker compose up to ensure clean configuration reload

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


Configuration File Location in Project Structure

The mosquitto.conf file is:

  • Located in the repository root directory
  • Tracked in version control (committed to Git)
  • Mounted into the container at runtime
  • Safe to modify and commit (contains no secrets)

This is in contrast to:

  • .env file: Contains secrets, excluded via .gitignore
  • data/ directory: Contains runtime data, excluded via .gitignore

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


Minimal Configuration Justification

The current mosquitto.conf implements a minimal viable configuration with only essential directives. This design choice provides:

Advantages

AspectBenefit
SimplicityEasy to understand for first-time users
Quick SetupNo complex authentication configuration required
Cloudflare SecurityNetwork-level protection offloaded to Cloudflare Tunnel
DebuggingFewer variables when troubleshooting connectivity

Omitted Configurations

The following common Mosquitto directives are not present in mosquitto.conf:1-6:

  • password_file: No password-based authentication
  • acl_file: No topic-level access control
  • persistence: No message persistence across restarts
  • log_dest: Uses default logging to stdout (captured by Docker)
  • max_connections: No connection limit imposed
  • max_queued_messages: Uses Mosquitto defaults
  • message_size_limit: Uses Mosquitto defaults

For implementations requiring these features, see Topic Access Control (ACL)) and Production Considerations.

Sources: mosquitto.conf:1-6


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

Relevant source files

This document explains the environment variables used by the Docker MQTT Mosquitto Cloudflare Tunnel system, how to configure them, and their role in service authentication and operation.

Environment variables provide configuration values to the Docker containers at runtime. In this system, the primary use of environment variables is to supply the Cloudflare tunnel authentication token to the cloudflared service without hardcoding credentials in version-controlled files.

For information about obtaining the Cloudflare tunnel token from the Zero Trust dashboard, see Cloudflare Tunnel Configuration. For information about version control practices that protect secrets, see Version Control Practices.

Sources : .env.sample1 README.md51


Environment Variables Overview

The system uses a single environment variable that must be configured before deployment:

Variable NameRequiredPurposeUsed ByDefault Value
CLOUDFLARE_TUNNEL_TOKENYesAuthenticates the cloudflared tunnel client with Cloudflare's networkcloudflared serviceNone (must be provided)

Sources : .env.sample1 README.md51


Configuration File Structure

The environment variable configuration follows a template-based approach to separate public documentation from private secrets:

Diagram: Environment Variable Configuration Flow

graph LR
    Sample[".env.sample\n(Version Controlled)"]
Env[".env\n(Not Version Controlled)"]
GitIgnore[".gitignore"]
ComposeYML["docker-compose.yml"]
CFService["cloudflared service"]
Sample -->|Developer copies| Env
 
   GitIgnore -->|Excludes| Env
 
   Env -->|Provides environment variables| ComposeYML
 
   ComposeYML -->|Injects CLOUDFLARE_TUNNEL_TOKEN| CFService
    
 
   Sample -.->|Committed to Git| Repo["GitHub Repository"]
Env -.->|Never committed| Repo

The .env.sample file serves as a template showing the required structure, while the actual .env file contains the real credentials and is excluded from version control by .gitignore.

Sources : .env.sample1 .gitignore1 README.md51


Setting Up the .env File

Step 1: Locate the Template

The repository contains a template file at .env.sample:1-2 with the following structure:

CLOUDFLARE_TUNNEL_TOKEN=your_token

Step 2: Create the .env File

Copy the template to create your local environment configuration:

Step 3: Populate the Token Value

Replace your_token with the actual tunnel token obtained from the Cloudflare Zero Trust dashboard. The token is a long alphanumeric string provided when creating a Cloudflare Tunnel.

Example .env file structure:

CLOUDFLARE_TUNNEL_TOKEN=eyJhIjoiYWJjMTIzZGVmNDU2IiwidCI6IjEyMzQ1Njc4LWFiY2QtZWZnaC1pamtsLW1ub3BxcnN0dXZ3eCIsInMiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejEyMzQ1Njc4OTAifQ==

Note : The actual token will be significantly longer than the example shown.

Step 4: Verify .gitignore Protection

Confirm that .env is listed in .gitignore1 to prevent accidental commits of credentials to version control:

.env

Sources : .env.sample:1-2 README.md51 .gitignore1


Environment Variable Injection Process

Diagram: Environment Variable Injection at Runtime

When docker compose up is executed, Docker Compose reads the .env file from the current directory and makes its variables available to service definitions. The cloudflared service accesses CLOUDFLARE_TUNNEL_TOKEN and passes it to the tunnel client command.

Sources : .env.sample1 README.md51


Variable Reference

CLOUDFLARE_TUNNEL_TOKEN

Purpose : Authenticates the cloudflared container with Cloudflare's tunnel infrastructure, establishing a secure, encrypted connection between the local Docker environment and Cloudflare's edge network.

Format : Base64-encoded JSON string containing:

  • Account identifier
  • Tunnel identifier
  • Secret key

How to Obtain : Generated by Cloudflare Zero Trust dashboard when creating a new tunnel. See README.md:47-51 for detailed instructions.

Usage in docker-compose.yml : Referenced in the cloudflared service definition as ${CLOUDFLARE_TUNNEL_TOKEN} and passed to the container's command line arguments.

Security Implications :

  • Grants full access to the specified Cloudflare tunnel
  • Should be treated as a sensitive credential with the same protection as passwords
  • Compromise of this token allows unauthorized access to route traffic through your tunnel
  • Token rotation requires creating a new tunnel in the Cloudflare dashboard

Sources : .env.sample1 README.md:47-51


Security Considerations

Protection from Version Control

The .env file is explicitly excluded from version control via .gitignore1 to prevent accidental exposure of the tunnel token. The .env.sample template is committed instead, providing documentation without exposing credentials.

Diagram: Version Control Protection Mechanism

graph TB
    subgraph "Version Controlled (Public)"
        Sample[".env.sample\nTemplate with placeholder"]
GitIgnore[".gitignore\nExcludes .env"]
end
    
    subgraph "Local Only (Private)"
        Env[".env\nContains real CLOUDFLARE_TUNNEL_TOKEN"]
end
    
    subgraph "Git Operations"
        Commit["git commit"]
Push["git push"]
end
    
 
   Sample -.->|Can be committed| Commit
 
   GitIgnore -.->|Can be committed| Commit
 
   Env -.->|Blocked by .gitignore| Commit
 
   Commit --> Push

Sources : .gitignore1 .env.sample1

Token Scope

The CLOUDFLARE_TUNNEL_TOKEN grants access only to the specific tunnel for which it was generated. Each tunnel has its own unique token, and tokens cannot be used interchangeably between tunnels.

Environment Variable Storage

Environment variables in Docker containers are:

  • Visible in container inspection commands (docker inspect)
  • Visible in process listings within the container
  • Not persisted to Docker images
  • Not logged by Docker daemon (unless explicitly configured)

For enhanced security in production environments, consider using Docker secrets or external secret management systems rather than .env files.

Sources : .env.sample1 .gitignore1


Troubleshooting

Missing .env File

Symptom : docker compose up fails with an error about undefined variables or the cloudflared service fails to start.

Solution : Ensure the .env file exists in the same directory as docker-compose.yml and contains the CLOUDFLARE_TUNNEL_TOKEN variable.

Invalid Token Format

Symptom : The cloudflared service starts but immediately fails with authentication errors.

Solution : Verify that:

  1. The token was copied completely from the Cloudflare dashboard (no truncation)
  2. No extra spaces or newlines were added
  3. The token corresponds to an active tunnel in your Cloudflare account

Token Not Being Read

Symptom : Environment variable appears empty in the container despite being set in .env.

Solution :

  1. Verify .env is in the same directory where docker compose up is executed
  2. Restart services: docker compose down then docker compose up
  3. Check that the variable name matches exactly (case-sensitive): CLOUDFLARE_TUNNEL_TOKEN

Sources : .env.sample1 README.md51


GitHub

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

The source code for this project is available on GitHub.

Deployment

Relevant source files

This document provides instructions for deploying the Docker MQTT Mosquitto Cloudflare Tunnel system using Docker Compose. It covers starting services, verifying successful deployment, managing service lifecycle, and troubleshooting deployment issues.

For configuration setup before deployment, see Environment Variables and Cloudflare Tunnel Configuration. For production deployment considerations, see Production Considerations.

Prerequisites Verification

Before deploying, ensure the following are in place:

RequirementVerification CommandExpected Result
Docker Enginedocker --versionDocker version 20.x or higher
Docker Composedocker compose versionDocker Compose version 2.x or higher
.env filetest -f .env && echo "exists""exists"
CLOUDFLARE_TUNNEL_TOKENgrep CLOUDFLARE_TUNNEL_TOKEN .envNon-empty token value
docker-compose.ymltest -f docker-compose.yml && echo "exists""exists"
mosquitto.conftest -f mosquitto.conf && echo "exists""exists"

Sources: README.md51 docker-compose.yml:1-18 mosquitto.conf:1-6

Starting Services

Basic Deployment

The primary deployment command starts both the mosquitto and cloudflared services as defined in docker-compose.yml:1-18:

This command runs in the foreground, displaying logs from both containers in real-time. Press Ctrl+C to stop the services.

Sources: README.md:70-72

Detached Mode

For production deployments, run services in detached mode to release the terminal:

The -d flag starts containers in the background. Services continue running after terminal closure due to the restart: unless-stopped policy defined in docker-compose.yml:9-15

Sources: docker-compose.yml:9-15

Deployment Process Flow

Sources: docker-compose.yml:1-18 README.md:70-72

Rebuild and Deploy

When configuration or code changes require rebuilding images:

This forces Docker to rebuild images before starting services. Note: This system uses pre-built images (eclipse-mosquitto:latest and cloudflare/cloudflared:latest), so rebuilding is typically unnecessary unless custom Dockerfiles are introduced.

Sources: docker-compose.yml:5-12

Verifying Deployment

Container Status Check

Verify both containers are running:

Expected output:

NAME          IMAGE                              STATUS
cloudflared   cloudflare/cloudflared:latest      Up X seconds
mosquitto     eclipse-mosquitto:latest           Up X seconds

Both containers should show STATUS as "Up" with no restart loops.

Sources: docker-compose.yml:6-13

Service-Specific Verification

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

Viewing Logs

Monitor service logs to verify successful startup:

Expected Log Patterns

mosquitto container:

  • Opening ipv4 listen socket on port 1883 - TCP listener started
  • Opening websockets listen socket on port 9001 - WebSocket listener started

cloudflared container:

  • Connection registered - Tunnel authenticated with Cloudflare
  • Started tunnel - Tunnel operational and routing traffic

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

Health Check Script

Implement a health check loop similar to the CI pipeline pattern:

This pattern is derived from the CI workflow health check implementation.

Sources: High-level diagram CI/CD Testing Pipeline, docker-compose.yml:6-13

Managing Service Lifecycle

Stopping Services

Stop all services gracefully:

This sends SIGTERM to containers, allowing clean shutdown. Containers are stopped but not removed.

Sources: docker-compose.yml:1-18

Stopping and Removing

Stop services and remove containers:

This removes containers and networks but preserves volumes and images. The restart: unless-stopped policy means services will not restart automatically after docker compose down.

Sources: docker-compose.yml:9-15

Restarting Services

Restart all services:

Restart specific service:

Sources: docker-compose.yml:4-11

Service Lifecycle States

Sources: docker-compose.yml:9-15

Updating Services

Pull latest images and restart:

Sources: docker-compose.yml:5-12

Deployment Modes Comparison

ModeCommandUse CaseLogs VisibleTerminal Blocked
Foregrounddocker compose upDevelopment, debuggingYes, real-timeYes
Detacheddocker compose up -dProduction, background servicesNo (use docker compose logs)No
Force Recreatedocker compose up --force-recreateConfig changes not detectedDepends on flagsDepends on flags
Build & Startdocker compose up --buildCustom image changesDepends on flagsDepends on flags

Sources: README.md:70-72 docker-compose.yml:1-18

Container Runtime Environment

Environment Variable Injection

The cloudflared service receives CLOUDFLARE_TUNNEL_TOKEN from the .env file via docker-compose.yml:16-17:

Docker Compose reads .env automatically and injects the variable into the container environment, where it's consumed by docker-compose.yml14:

Sources: docker-compose.yml:13-17

Volume Mount Configuration

The mosquitto service mounts its configuration file as specified in docker-compose.yml:7-8:

This creates a read-only bind mount, allowing configuration changes without rebuilding the container. Restart the mosquitto service to apply configuration changes:

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

Network Architecture

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

Deployment Checklist

Use this checklist to verify successful deployment:

  • Prerequisites verified (Docker, Docker Compose installed)
  • .env file created with CLOUDFLARE_TUNNEL_TOKEN
  • docker-compose.yml present in working directory
  • mosquitto.conf present in working directory
  • Cloudflare Tunnel configured with public hostname
  • docker compose up -d executed without errors
  • docker compose ps shows both containers running
  • docker compose logs mosquitto shows listener messages
  • docker compose logs cloudflared shows tunnel connection registered
  • MQTT client can connect via Cloudflare public hostname
  • Messages publish and subscribe successfully

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

Common Deployment Issues

Token Not Found

Symptom: cloudflared container exits immediately

Cause: CLOUDFLARE_TUNNEL_TOKEN not set in .env

Solution:

Sources: docker-compose.yml:16-17

Port Conflicts

Symptom: mosquitto fails to start with "address already in use"

Cause: Another process using ports 1883 or 9001

Solution:

Sources: mosquitto.conf:1-4

Configuration File Not Found

Symptom: mosquitto container restarts repeatedly

Cause: mosquitto.conf missing or incorrect path in volume mount

Solution:

Sources: docker-compose.yml:7-8

Tunnel Connection Failure

Symptom: cloudflared logs show connection errors

Cause: Invalid token or network connectivity issues

Solution:

  1. Verify token in Cloudflare Zero Trust dashboard
  2. Check network connectivity: ping cloudflare.com
  3. Review cloudflared logs: docker compose logs cloudflared
  4. Regenerate token if expired

Sources: docker-compose.yml:13-17

Post-Deployment Validation

After deployment, validate the complete system:

Sources: docker-compose.yml:1-18 README.md:60-66


GitHub

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

The source code for this project is available on GitHub.

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


GitHub

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

The source code for this project is available on GitHub.

MQTT 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.

Listener Configuration Structure

ListenerPortProtocolAnonymous AccessPrimary Use Case
mosquitto.conf11883Standard MQTT/TCPEnabled globally mosquitto.conf2Direct MQTT client connections
mosquitto.conf:4-59001MQTT over WebSocketsEnabled globally mosquitto.conf2Browser-based clients, Cloudflare tunnel

Sources : mosquitto.conf


Listener 1883: Standard MQTT TCP

The first listener, configured at mosquitto.conf1 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 mosquitto.conf1: Binds the Mosquitto broker to TCP port 1883
  • allow_anonymous true mosquitto.conf2: 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.

Sources : mosquitto.conf, README.md

graph LR
    subgraph "Docker Internal Network"
        OtherContainer["Other Docker Container"]
MosquittoPort1883["mosquitto:1883\nTCP Listener"]
end
    
    subgraph "External Network"
        Internet["Public Internet"]
end
    
 
   OtherContainer -->|Direct MQTT/TCP| MosquittoPort1883
 
   Internet -.->|Not Accessible| MosquittoPort1883

Listener 9001: MQTT over WebSockets

The second listener, configured at mosquitto.conf:4-5 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: Binds the Mosquitto broker to TCP port 9001
  • protocol websockets mosquitto.conf5: 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 routing external MQTT WebSocket connections through the cloudflared container to this listener.

sequenceDiagram
    participant Client as "External MQTT Client"
    participant CFEdge as "Cloudflare Edge"
    participant Cloudflared as "cloudflared Container"
    participant Mosquitto9001 as "mosquitto:9001\nWebSocket Listener"
    participant MosqConf as "mosquitto.conf"
    
    Note over Client: Initiates MQTT over WebSocket
    Client->>CFEdge: WebSocket Upgrade Request\nto public hostname
    CFEdge->>Cloudflared: Route through encrypted tunnel
    Cloudflared->>Mosquitto9001: HTTP proxy to mosquitto:9001
    
    Mosquitto9001->>MosqConf: Check listener configuration
    Note over MosqConf: listener 9001\nprotocol websockets\nallow_anonymous true
    MosqConf-->>Mosquitto9001: Accept WebSocket connection
    
    Mosquitto9001-->>Cloudflared: WebSocket established
    Cloudflared-->>CFEdge: Through tunnel
    CFEdge-->>Client: WebSocket connection established
    
    Note over Client,Mosquitto9001: MQTT messages flow over WebSocket

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


graph TB
    subgraph "External Access"
        PublicClient["MQTT WebSocket Client\nBrowser/JS Library"]
PublicHostname["Public Hostname\nConfigured in CF Dashboard"]
end
    
    subgraph "Cloudflare Infrastructure"
        CFEdge["Cloudflare Edge Network"]
CFTunnel["Cloudflare Tunnel\nEncrypted Connection"]
end
    
    subgraph "Docker Host"
        subgraph "cloudflared Container"
            CFD["cloudflared process"]
Proxy["HTTP Proxy"]
end
        
        subgraph "mosquitto Container"
            MosqBroker["Mosquitto Broker"]
L1883["Listener 1883\nprotocol: MQTT/TCP"]
L9001["Listener 9001\nprotocol: websockets"]
end
        
        subgraph "Docker Internal Network"
            InternalClient["Internal Container\nDirect MQTT Client"]
end
    end
    
 
   PublicClient -->|MQTT over WS| PublicHostname
 
   PublicHostname --> CFEdge
 
   CFEdge -->|Encrypted Tunnel| CFTunnel
 
   CFTunnel --> CFD
 
   CFD --> Proxy
 
   Proxy -->|mosquitto:9001| L9001
    
 
   L9001 --> MosqBroker
 
   L1883 --> MosqBroker
    
 
   InternalClient -->|mosquitto:1883 Direct TCP| L1883
    
    style L9001 fill:#e1f5ff
    style L1883 fill:#fff3e1

Traffic Routing Architecture

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

Routing Configuration References :

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: listener 1883 - Binds to port 1883 on all interfaces (no bind_address specified)
  • mosquitto.conf4: 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: 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: 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 for detailed security implications.

Configuration Interaction Matrix

Configuration AspectListener 1883Listener 9001
Port1883 mosquitto.conf19001 mosquitto.conf4
ProtocolMQTT/TCP (implicit default)WebSockets mosquitto.conf5
Anonymous AccessEnabled mosquitto.conf2Enabled mosquitto.conf2
Cloudflare RoutingNot configuredConfigured README.md62
External AccessibilityDocker internal onlyVia 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 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


GitHub

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

The source code for this project is available on GitHub.

Anonymous Access

Relevant source files

This document explains the anonymous access configuration in the Mosquitto MQTT broker, including its implementation, security implications, and appropriate use cases. Anonymous access allows any MQTT client that can reach the broker to connect and publish/subscribe to any topic without authentication credentials.

For information about implementing authentication and topic-based access control, see Topic Access Control (ACL)). For the broader security architecture of the system, see Security Model.


Configuration Overview

The Mosquitto broker is configured to allow anonymous access through a single directive in the configuration file. This setting controls whether clients can connect without providing authentication credentials.

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 Listeners for details on these listeners).

Sources: mosquitto.conf


How Anonymous Access Works

Anonymous Access Connection Flow

When allow_anonymous true is set, 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.

Sources: mosquitto.conf, README.md


Access Permissions

Unrestricted Topic 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

With anonymous access enabled and no ACL (Access Control List) file configured, clients have complete freedom to:

  • Subscribe to any topic pattern, including global wildcards like #
  • Publish messages to any topic
  • Set and clear retained messages
  • Access all Quality of Service (QoS) levels

No Authentication Layer

graph TB
    Client["External MQTT Client"]
CF["cloudflared Container"]
Mosquitto["mosquitto Container"]
subgraph "Authentication Check"
        CheckAnon["allow_anonymous: true"]
NoAuth["No Credential Validation"]
end
    
 
   Client -->|MQTT CONNECT| CF
 
   CF -->|Proxy to mosquitto:9001| Mosquitto
 
   Mosquitto --> CheckAnon
 
   CheckAnon --> NoAuth
 
   NoAuth -->|CONNACK Success| Mosquitto
 
   Mosquitto -->|Success| CF
 
   CF -->|Success| Client
    
    Note1["No username check"]
Note2["No password check"]
Note3["No ACL evaluation"]
NoAuth -.-> Note1
 
   NoAuth -.-> Note2
 
   NoAuth -.-> Note3

The system flow shows that no authentication validation occurs:

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

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

Sources: README.md


Configuration File Structure

The complete Mosquitto configuration that enables anonymous access:

listener 1883
allow_anonymous true

listener 9001
protocol websockets

Configuration Analysis

LineDirectiveEffect
1listener 1883Defines standard MQTT listener
2allow_anonymous trueEnables anonymous access globally
3(blank)
4listener 9001Defines WebSocket listener
5protocol websocketsSpecifies WebSocket protocol for listener on 9001

The allow_anonymous true directive on line 2 applies to both listeners. There is no ACL file specified, no password file, and no per-listener authentication override. This creates a uniform anonymous access policy across all connection methods.

File Location: mosquitto.conf:1-6

Sources: mosquitto.conf


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)


Comparison with Protected Configuration

The main branch uses anonymous access for simplicity. An alternative implementation exists in the [protected-no-wildcard branch](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/8a829fda/protected-no-wildcard branch) that demonstrates restricted access patterns.

Configuration Differences

AspectMain Branch (Anonymous)Protected-No-Wildcard Branch
AuthenticationNone requiredUsername/password required
Topic AccessUnrestrictedACL file with per-user restrictions
Wildcard SubscriptionsFully allowedRestricted by ACL rules
Configuration ComplexityMinimal (6 lines)Additional ACL file management
Use CaseTrusted environmentsMulti-tenant or production systems

The protected branch adds:

  • An ACL file that restricts topic access by username
  • Password file for credential validation
  • Topic-level permissions enforcement
  • Encryption for retained messages using gocryptfs

For details on implementing authentication and topic restrictions, see Topic Access Control (ACL)).

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/8a829fda/protected-no-wildcard branch) provides a complete reference implementation. View the [configuration differences](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/8a829fda/configuration differences) to understand required changes.

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

Sources: README.md


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

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/8a829fda/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


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 Service

Relevant source files

Purpose and Scope

This document provides in-depth technical documentation of the cloudflared service, which establishes a secure, outbound-only tunnel connection between the Docker host environment and Cloudflare's edge network. It covers the Docker container configuration, tunnel authentication mechanism, traffic routing behavior, and operational characteristics of the tunnel client.

For information about configuring the Cloudflare Tunnel through the Zero Trust dashboard and obtaining the tunnel token, see Cloudflare Tunnel Configuration. For information about the Mosquitto service that cloudflared routes traffic to, see Mosquitto MQTT Broker.


Service Overview

The cloudflared service runs the official Cloudflare Tunnel client (cloudflare/cloudflared:latest) as a Docker container. Its primary responsibility is to establish and maintain an encrypted, outbound-only tunnel connection to Cloudflare's global edge network. This tunnel enables external MQTT clients to reach the internal mosquitto service without requiring inbound firewall rules or exposing ports directly to the internet.

The service operates as a reverse proxy within the Docker Compose stack, forwarding all traffic received from the Cloudflare edge through the tunnel to the mosquitto container's WebSocket listener on port 9001.

Sources: docker-compose.yml:11-17 README.md:15-20


Docker Container Configuration

Service Definition

The cloudflared service is defined in docker-compose.yml:11-17 with the following configuration:

Configuration AspectValuePurpose
Service NamecloudflaredIdentifier used by Docker Compose
Imagecloudflare/cloudflared:latestOfficial Cloudflare Tunnel client image
Container NamecloudflaredHostname accessible within Docker network
Commandtunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}Starts tunnel with authentication token
Restart Policyunless-stoppedAutomatically restarts on failure or reboot
Environment VariablesCLOUDFLARE_TUNNEL_TOKENTunnel authentication credential

Command Line Arguments

The container executes with three key command line arguments:

  • tunnel - Invokes the tunnel subcommand of cloudflared
  • --no-autoupdate - Prevents automatic updates of the cloudflared binary (recommended for containerized deployments where the Docker image should control versioning)
  • run --token ${CLOUDFLARE_TUNNEL_TOKEN} - Runs the tunnel using token-based authentication

Sources: docker-compose.yml14


Container Configuration Diagram

Diagram: cloudflared Service Configuration Flow

This diagram illustrates how the service definition in docker-compose.yml translates to a running container, with the authentication token sourced from the .env file.

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


Tunnel Authentication Mechanism

Authentication Token

The cloudflared service authenticates with Cloudflare's edge network using CLOUDFLARE_TUNNEL_TOKEN, an opaque authentication credential generated by the Cloudflare Zero Trust dashboard. This token:

  1. Uniquely identifies the tunnel - Each tunnel has a unique token that maps to a specific tunnel configuration in Cloudflare's system
  2. Contains embedded authentication - The token includes all necessary authentication information; no username/password is required
  3. Associates with routing rules - The token links to public hostname configurations that determine traffic routing

The token is provided to the container via the CLOUDFLARE_TUNNEL_TOKEN environment variable, which Docker Compose populates from the .env file docker-compose.yml17

Token Security

The token grants full control over the tunnel and must be protected:

  • Never committed to version control - The .env file is excluded via .gitignore
  • Stored only in local environment - Each deployment maintains its own .env file
  • Template provided - .env.sample1 shows the required format without exposing actual credentials

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


Tunnel Establishment Process

sequenceDiagram
    participant Compose as "docker-compose"
    participant Container as "cloudflared Container"
    participant Token as "CLOUDFLARE_TUNNEL_TOKEN"
    participant CFEdge as "Cloudflare Edge Network"
    participant CFControl as "Cloudflare Control Plane"
    
    Compose->>Container: Start container with command:\ntunnel --no-autoupdate run --token
    Container->>Token: Read environment variable
    Token-->>Container: Return token value
    
    Container->>CFControl: Authenticate with token
    CFControl->>CFControl: Validate token\nRetrieve tunnel configuration
    CFControl-->>Container: Authentication successful\nRouting rules provided
    
    Container->>CFEdge: Establish outbound encrypted connection\n(typically TLS over TCP)
    CFEdge-->>Container: Connection established
    
    Container->>Container: Enter ready state\nListening for tunnel traffic
    
    Note over Container,CFEdge: Tunnel is now active and ready to route traffic

Startup Sequence

When the cloudflared container starts, it follows this sequence to establish the tunnel:

Diagram: Cloudflared Tunnel Establishment Sequence

This diagram shows the complete startup sequence from container launch to tunnel readiness.

Sources: docker-compose.yml14 README.md:47-54

Connection Characteristics

The tunnel connection exhibits these characteristics:

CharacteristicBehavior
DirectionOutbound-only from cloudflared to Cloudflare edge
ProtocolEncrypted (TLS) over TCP
PersistenceLong-lived connection, automatically reconnects if dropped
Firewall RequirementsNone - no inbound ports need to be opened
Restart BehaviorContainer restarts automatically (unless-stopped policy) if tunnel fails

Sources: docker-compose.yml15


Traffic Routing and Proxying

Routing Configuration

The cloudflared service acts as a reverse proxy, forwarding traffic from Cloudflare's edge to the internal mosquitto service. The routing configuration is established through the Cloudflare Zero Trust dashboard, not in the Docker configuration:

  1. Public hostname - Configured in Cloudflare dashboard (e.g., mqtt.example.com)
  2. Service type - Set to HTTP in the dashboard
  3. Target URL - Set to mosquitto:9001 README.md62
graph LR
    subgraph "External"
        Client["MQTT Client"]
CFEdge["Cloudflare Edge\n(Public IP)"]
end
    
    subgraph "Docker Host"
        subgraph "Docker Internal Network"
            CFContainer["cloudflared Container"]
MosqContainer["mosquitto Container\n(container_name: mosquitto)"]
end
    end
    
    subgraph "Cloudflare Dashboard Config"
        PublicHost["Public Hostname:\nmqtt.example.com"]
ServiceURL["Service URL:\nmosquitto:9001"]
end
    
 
   Client -->|MQTT over Internet| CFEdge
 
   CFEdge -->|Encrypted Tunnel| CFContainer
 
   CFContainer -->|HTTP Proxy to mosquitto:9001| MosqContainer
    
 
   PublicHost -.->|Maps to| CFEdge
 
   ServiceURL -.->|Resolves via Docker DNS| MosqContainer

The hostname mosquitto in the target URL resolves to the mosquitto container via Docker's internal DNS, using the container_name field from the mosquitto service definition.

Traffic Flow

Diagram: Cloudflared Traffic Routing Architecture

This diagram illustrates the complete traffic path from external clients through the tunnel to the mosquitto service, showing how the mosquitto:9001 service URL resolves within Docker's network.

Sources: README.md:59-66 docker-compose.yml:6-13


Network Behavior

Internal Docker Networking

The cloudflared service participates in Docker Compose's default bridge network alongside the mosquitto service. This enables:

  1. DNS-based service discovery - The hostname mosquitto automatically resolves to the IP address of the mosquitto container
  2. Direct container-to-container communication - Traffic between cloudflared and mosquitto stays within the Docker network
  3. No port exposure required - Neither service exposes ports to the host machine

No Inbound Port Requirements

A key architectural benefit of the cloudflared tunnel is that it requires no inbound firewall rules. The tunnel connection is established outbound from the cloudflared container to Cloudflare's edge network. All traffic flows through this pre-established tunnel, eliminating the need to expose any ports on the Docker host to the public internet.

Sources: docker-compose.yml:1-17 README.md:15-20


Operational Characteristics

Automatic Restart Behavior

The service is configured with restart: unless-stopped docker-compose.yml15 which means:

ScenarioBehavior
Container crashesAutomatically restarts
Docker daemon restartsContainer starts automatically
System rebootContainer starts automatically
Manual stopContainer remains stopped until manually started

This restart policy ensures high availability of the tunnel connection without manual intervention.

Update Management

The --no-autoupdate flag docker-compose.yml14 prevents cloudflared from automatically updating itself. This is recommended for containerized deployments because:

  • Version control - Updates are managed by changing the Docker image tag, not by in-container updates
  • Predictability - The deployed version matches the image version
  • Consistency - All instances run the same version of cloudflared

To update cloudflared, pull a newer version of the cloudflare/cloudflared image and recreate the container.

Sources: docker-compose.yml:14-15


Authentication and Token Management Diagram

Diagram: Token Lifecycle and Security

This diagram traces the complete lifecycle of the tunnel authentication token, from generation in the Cloudflare dashboard to consumption by the cloudflared container, highlighting the security boundaries.

Sources: docker-compose.yml:16-17 .env.sample1 README.md:47-51


Integration with Mosquitto Service

Service Dependency

While the docker-compose.yml file does not explicitly define a depends_on relationship, the cloudflared service functionally depends on the mosquitto service being available:

  1. Routing target - The tunnel routes traffic to mosquitto:9001
  2. Service availability - If mosquitto is not running, routed traffic will fail
  3. Docker DNS resolution - The hostname mosquitto must resolve for proxying to work

The lack of an explicit dependency is acceptable because:

  • Both services have restart: unless-stopped policies, ensuring they both remain running
  • Cloudflared will queue or retry connections if mosquitto is temporarily unavailable
  • The tunnel itself remains active even if the backend service is down

Port Mapping

The cloudflared service routes traffic specifically to port 9001 on the mosquitto container README.md62 This port corresponds to mosquitto's WebSocket listener configuration. The choice of port 9001 rather than 1883 (standard MQTT) is significant because:

  • Cloudflare Tunnel works with HTTP/WebSocket protocols
  • MQTT over WebSockets is supported on port 9001
  • Standard MQTT (port 1883) uses raw TCP, which requires WebSocket encapsulation when tunneled

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


Operational Monitoring

Container Status

The health and status of the cloudflared service can be monitored using standard Docker commands:

Expected Log Output

When functioning correctly, the cloudflared container logs will show:

  • Successful authentication with Cloudflare
  • Tunnel registration confirmation
  • Active connection status
  • Traffic routing information

Common Operational States

StateIndicationMeaning
RunningContainer status shows "Up"Tunnel is active and routing traffic
RestartingContainer repeatedly starts and stopsAuthentication failure or network issue
ExitedContainer stopped with exit codeConfiguration error or manual stop

Sources: docker-compose.yml:11-17


Summary

The cloudflared service provides secure, managed tunnel connectivity from Cloudflare's global edge network to the internal mosquitto MQTT broker. Key characteristics include:

  • Container: cloudflare/cloudflared:latest running with container_name: cloudflared
  • Authentication: Token-based via CLOUDFLARE_TUNNEL_TOKEN environment variable
  • Command: tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}
  • Restart Policy: unless-stopped for high availability
  • Network Role: Reverse proxy forwarding traffic to mosquitto:9001
  • Security Model: Outbound-only connection, no inbound firewall rules required
  • Configuration Management: Tunnel routing configured in Cloudflare dashboard, not in Docker files

Sources: docker-compose.yml:11-17 README.md:15-73 .env.sample1


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

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


GitHub

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

The source code for this project is available on GitHub.

Development

Relevant source files

Purpose and Scope

This document provides an overview of the development workflow and practices for the Docker MQTT Mosquitto Cloudflare Tunnel system. It covers the essential processes for setting up a local development environment, understanding the automated testing pipeline, and following version control best practices.

This page serves as an entry point for developers who want to contribute to or extend the system. For detailed instructions on:

For production deployment guidance, see Production Considerations.

Sources: README.md, docker-compose.yml, .gitignore, .github/workflows/ci.yml


Development Workflow Overview

The development workflow follows a standard pattern for Docker-based systems with cloud integration. Developers work with configuration files tracked in version control while managing secrets locally.

Development File Structure

The following table summarizes the files developers interact with and their roles:

FileVersion ControlledPurposeDeveloper Action
docker-compose.ymlYesService orchestrationModify service definitions
mosquitto.confYesMQTT broker configurationAdjust broker settings
.env.sampleYesEnvironment variable templateReference for configuration
.envNoActual secrets and tokensCreate locally from template
.gitignoreYesDefines excluded filesUpdate when adding new excludes
.github/workflows/ci.ymlYesCI/CD pipeline definitionModify test procedures
data/*NoRuntime data and stateCreated by mosquitto at runtime

Sources: .gitignore, docker-compose.yml, .env.sample (referenced in README)

graph TB
    subgraph "Version Control [GitHub]"
        Repo["Repository"]
ComposeYML["docker-compose.yml"]
MosqConf["mosquitto.conf"]
EnvSample[".env.sample"]
GitIgnore[".gitignore"]
CIWorkflow[".github/workflows/ci.yml"]
end
    
    subgraph "Developer Workstation"
        LocalClone["Local Git Clone"]
EnvActual[".env\n(Not Tracked)"]
DataDir["data/\n(Not Tracked)"]
DockerEngine["Docker Engine"]
end
    
    subgraph "External Services"
        CFDashboard["Cloudflare Zero Trust Dashboard"]
CFToken["CLOUDFLARE_TUNNEL_TOKEN"]
end
    
    subgraph "Running Services [Docker Compose]"
        MosquittoContainer["mosquitto Container\n(eclipse-mosquitto:latest)"]
CloudflaredContainer["cloudflared Container\n(cloudflare/cloudflared:latest)"]
end
    
 
   Repo -->|git clone| LocalClone
 
   LocalClone -->|Contains| ComposeYML
 
   LocalClone -->|Contains| MosqConf
 
   LocalClone -->|Contains| EnvSample
 
   LocalClone -->|Contains| GitIgnore
    
 
   GitIgnore -.->|Excludes| EnvActual
 
   GitIgnore -.->|Excludes| DataDir
    
 
   EnvSample -->|Template for| EnvActual
 
   CFDashboard -->|Provides| CFToken
 
   CFToken -->|Stored in| EnvActual
    
 
   ComposeYML -->|docker-compose up| DockerEngine
 
   DockerEngine -->|Starts| MosquittoContainer
 
   DockerEngine -->|Starts| CloudflaredContainer
    
 
   MosqConf -.->|Volume Mount| MosquittoContainer
 
   EnvActual -.->|Environment Variable| CloudflaredContainer
 
   MosquittoContainer -.->|Creates| DataDir

Development Environment Components

Diagram: Development Environment Component Relationships

This diagram shows how version-controlled files, local secrets, and Docker services interact during development. The .gitignore file .gitignore:1-2 prevents sensitive files from being committed, while docker-compose.yml docker-compose.yml:1-18 orchestrates both services.

Sources: .gitignore, docker-compose.yml, .env.sample (referenced in README)


Continuous Integration Pipeline

The system includes automated testing through GitHub Actions that validates the mosquitto service can start successfully.

graph TB
    subgraph "Trigger Events"
        PushMain["Push to main branch"]
PullRequest["Pull Request to main"]
end
    
    subgraph "GitHub Actions Runner [ubuntu-latest]"
        Checkout["Checkout repository\n(actions/checkout@v2)"]
SetupDocker["Install docker-compose\n(apt-get install)"]
StartService["docker-compose up -d mosquitto"]
subgraph "Health Check Loop"
            Iterate["for i in {1..10}"]
Inspect["docker inspect --format='{{.State.Status}}' mosquitto"]
CheckRunning{"Status == 'running'?"}
Sleep["sleep 10"]
end
        
        Teardown["docker-compose down"]
end
    
    subgraph "Test Result"
        Success["Exit 0: Success"]
Failure["Exit 1: Timeout"]
end
    
 
   PushMain -->|Triggers| Checkout
 
   PullRequest -->|Triggers| Checkout
    
 
   Checkout --> SetupDocker
 
   SetupDocker --> StartService
 
   StartService --> Iterate
    
 
   Iterate --> Inspect
 
   Inspect --> CheckRunning
 
   CheckRunning -->|Yes| Success
 
   CheckRunning -->|No| Sleep
 
   Sleep --> Iterate
 
   Iterate -.->|After 10 attempts| Failure
    
 
   Success --> Teardown
 
   Failure --> Teardown

CI Workflow Structure

Diagram: CI Workflow Execution Flow

The CI workflow defined in .github/workflows/ci.yml:1-43 executes a series of steps to validate the mosquitto service. The health check loop .github/workflows/ci.yml:28-39 polls the container status using docker inspect with a maximum of 10 attempts at 10-second intervals.

Sources: .github/workflows/ci.yml


Service Orchestration

The docker-compose.yml file defines two services that developers work with:

mosquitto Service

The mosquitto service configuration docker-compose.yml:4-9 includes:

  • Image: eclipse-mosquitto:latest
  • Container Name: mosquitto
  • Volume Mount: ./mosquitto.conf:/mosquitto/config/mosquitto.conf
  • Restart Policy: unless-stopped

This service does not expose any ports to the host, as traffic routing is handled internally through the Docker network to the cloudflared service.

cloudflared Service

The cloudflared service configuration docker-compose.yml:11-17 includes:

  • Image: cloudflare/cloudflared:latest
  • Container Name: cloudflared
  • Command: tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}
  • Environment: Receives CLOUDFLARE_TUNNEL_TOKEN from .env file
  • Restart Policy: unless-stopped

Sources: docker-compose.yml


Version Control Strategy

The repository implements a clear separation between public configuration and private secrets:

Tracked Files

Files committed to version control:

  • Service definitions (docker-compose.yml)
  • Public configuration (mosquitto.conf)
  • Environment templates (.env.sample)
  • CI workflows (.github/workflows/ci.yml)

Excluded Files

Files excluded via .gitignore .gitignore:1-2:

  • .env - Contains the CLOUDFLARE_TUNNEL_TOKEN secret
  • data/* - Runtime data directory used by mosquitto for persistence

This exclusion pattern ensures sensitive credentials never enter version control while maintaining clear documentation through .env.sample.

Sources: .gitignore


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


GitHub

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

The source code for this project is available on GitHub.

Local Development Setup

Relevant source files

Purpose and Scope

This document provides a guide for developers setting up a local development environment for the Docker MQTT Mosquitto Cloudflare Tunnel system. It covers repository cloning, file structure, configuration management, and running the system locally for development purposes.

For information about system prerequisites (Docker installation, Cloudflare account requirements), see Prerequisites. For detailed Cloudflare Tunnel setup, see Cloudflare Tunnel Configuration. For in-depth version control practices and .gitignore policies, see Version Control Practices. For production deployment procedures, see Deployment.


Local File Structure

The repository contains version-controlled configuration files and excludes sensitive credentials and runtime data from version control.

graph TB
    subgraph "Version Controlled"
        dockercompose["docker-compose.yml\nService Orchestration"]
mosquittoconf["mosquitto.conf\nBroker Configuration"]
envsample[".env.sample\nEnvironment Template"]
gitignore[".gitignore\nExclusion Rules"]
end
    
    subgraph "Excluded from Version Control"
        envfile[".env\nActual Secrets"]
datadir["data/*\nRuntime Data"]
end
    
 
   gitignore -->|Excludes| envfile
 
   gitignore -->|Excludes| datadir
 
   envsample -.->|Template for| envfile
    
 
   dockercompose -->|References ${CLOUDFLARE_TUNNEL_TOKEN}| envfile
 
   mosquittoconf -.->|May write to| datadir

Version-Controlled vs. Excluded Files

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

The .gitignore file explicitly excludes two items from version control:

Excluded ItemPurposeReason for Exclusion
.envContains CLOUDFLARE_TUNNEL_TOKENSensitive authentication credential
data/*Runtime data generated by MosquittoInstance-specific, not portable

Sources : .gitignore:1-2


Development Workflow

Initial Repository Setup

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

Step-by-Step Setup Process

1. Clone the Repository

2. Create Local Environment File

The .env.sample file serves as a template for the actual .env file:

.env.sample:1-3

Create your local .env file:

The newly created .env file is automatically excluded from version control by the .gitignore configuration at .gitignore1

Sources : .env.sample:1-3 .gitignore1

3. Obtain Cloudflare Tunnel Token

The CLOUDFLARE_TUNNEL_TOKEN must be obtained from the Cloudflare Zero Trust dashboard. This token authenticates the cloudflared service with Cloudflare's infrastructure. For detailed instructions on creating a tunnel and obtaining the token, see Cloudflare Tunnel Configuration.

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

4. Configure Environment Variables

Edit the .env file and replace the placeholder value:

CLOUDFLARE_TUNNEL_TOKEN=your_actual_token_here

The docker-compose.yml file references this environment variable at docker-compose.yml14 and docker-compose.yml17 passing it to the cloudflared container.

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


graph LR
    subgraph "Configuration Files"
        dockercompose["docker-compose.yml"]
mosquittoconf["mosquitto.conf"]
envfile[".env"]
end
    
    subgraph "Docker Services"
        mosquitto["mosquitto\nContainer"]
cloudflared["cloudflared\nContainer"]
end
    
 
   dockercompose -->|Orchestrates both services| mosquitto
 
   dockercompose -->|Orchestrates both services| cloudflared
    
 
   mosquittoconf -->|Volume mount at /mosquitto/config/mosquitto.conf| mosquitto
    
 
   envfile -->|Environment variable CLOUDFLARE_TUNNEL_TOKEN| cloudflared

Configuration File Mapping

The following diagram maps each configuration file to its purpose and the service that consumes it:

Sources : docker-compose.yml:1-18

Configuration File Details

FilePurposeConsumerMount/Injection Method
docker-compose.ymlService orchestrationDocker ComposeN/A
mosquitto.confBroker settingsmosquitto containerVolume mount at line 8
.envSecrets and tokenscloudflared containerEnvironment variable at lines 16-17

The docker-compose.yml file defines the volume mount for mosquitto.conf at docker-compose.yml8:

The .env file's CLOUDFLARE_TUNNEL_TOKEN is injected into the cloudflared service as an environment variable at docker-compose.yml:16-17:

Sources : docker-compose.yml:7-9 docker-compose.yml:16-17


Running the System Locally

Service Startup

Start both services in detached mode:

This command:

  1. Reads docker-compose.yml for service definitions
  2. Loads environment variables from .env
  3. Starts the mosquitto container with the volume-mounted mosquitto.conf
  4. Starts the cloudflared container with the CLOUDFLARE_TUNNEL_TOKEN environment variable

Sources : docker-compose.yml:1-18

Service Definitions

The docker-compose.yml defines two services:

mosquitto Service

docker-compose.yml:4-9

PropertyValuePurpose
imageeclipse-mosquitto:latestOfficial Mosquitto MQTT broker image
container_namemosquittoFixed container name for service discovery
volumes./mosquitto.conf:/mosquitto/config/mosquitto.confMounts local configuration into container
restartunless-stoppedAutomatic restart policy

cloudflared Service

docker-compose.yml:11-17

PropertyValuePurpose
imagecloudflare/cloudflared:latestOfficial Cloudflare Tunnel client image
container_namecloudflaredFixed container name for service discovery
commandtunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}Tunnel client startup with token substitution
restartunless-stoppedAutomatic restart policy
environmentCLOUDFLARE_TUNNEL_TOKENPasses token from .env file

Sources : docker-compose.yml:4-17


Local Data Directory

The data/ directory is excluded from version control at .gitignore2 This directory may be used by Mosquitto for persistent storage of retained messages or other runtime data. The directory is instance-specific and should not be committed to version control.

For encrypted retained message storage available in the protected-no-wildcard branch, see Encrypted Retained Messages.

Sources : .gitignore2


Development vs. Production Differences

Local Development Configuration

In local development:

  • The .env file is manually created and maintained by each developer
  • The data/ directory contains local runtime state
  • Services restart automatically via unless-stopped policy at docker-compose.yml9 and docker-compose.yml15
  • Configuration changes require service restart

Version Control Exclusions

The .gitignore configuration ensures that sensitive and instance-specific files never enter version control:

.gitignore:1-2

PatternMatchesReason
.envExact file matchContains CLOUDFLARE_TUNNEL_TOKEN secret
data/*All files in data/ directoryInstance-specific runtime data

Sources : .gitignore:1-2


Verifying Local Setup

After starting services, verify that both containers are running:

Expected output should show both mosquitto and cloudflared containers with status "Up".

To view logs:

For detailed troubleshooting procedures, see Troubleshooting.

Sources : docker-compose.yml6 docker-compose.yml13


Making Configuration Changes

Modifying Mosquitto Configuration

To change Mosquitto settings:

  1. Edit mosquitto.conf locally
  2. Restart the mosquitto service:

The volume mount at docker-compose.yml8 ensures the local file is immediately reflected in the container.

Modifying Environment Variables

To change the Cloudflare tunnel token or add new environment variables:

  1. Edit .env file
  2. Restart the cloudflared service:

Environment variables are loaded at container startup and referenced at docker-compose.yml:16-17

Sources : docker-compose.yml:7-9 docker-compose.yml:16-17


Summary

Local development setup requires:

  1. Repository cloning from GitHub
  2. Environment file creation by copying .env.sample to .env
  3. Token acquisition from Cloudflare Dashboard
  4. Service startup via docker-compose up -d

The system maintains a clear separation between version-controlled configuration (.env.sample, docker-compose.yml, mosquitto.conf) and excluded sensitive/runtime data (.env, data/*). This separation ensures secure credential management while maintaining reproducible configuration across development environments.

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


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 Pipeline

Relevant source files

Purpose and Scope

This document describes the automated testing pipeline implemented through GitHub Actions that validates the Mosquitto MQTT broker service can be successfully deployed using Docker Compose. The pipeline is triggered on code changes and performs basic health checks to ensure the broker container starts and reaches a running state.

Note : This page covers automated testing of the core infrastructure. For manual testing and local development setup, see Local Development Setup. For production deployment considerations, see Production Considerations.

Overview

The CI/CD pipeline is implemented as a GitHub Actions workflow defined in .github/workflows/ci.yml:1-43 The pipeline focuses exclusively on testing the mosquitto service, as the cloudflared service requires a valid CLOUDFLARE_TUNNEL_TOKEN which cannot be securely provided in the public CI environment.

Key Characteristics:

  • Scope : Tests only the mosquitto service from docker-compose.yml:4-9
  • Trigger Events : Push and pull request events to the main branch
  • Test Environment : Ubuntu runner with Docker and Docker Compose
  • Validation Method : Container state inspection with retry logic
  • Test Duration : Maximum 100 seconds (10 attempts × 10 seconds)

Sources: .github/workflows/ci.yml:1-43 docker-compose.yml:1-18

Workflow Configuration

Trigger Conditions

The workflow is configured to run on two types of GitHub events:

Event TypeBranchesConfiguration
pushmain.github/workflows/ci.yml:4-6
pull_requestmain.github/workflows/ci.yml:7-9

This ensures all changes to the main branch and all pull requests targeting main are automatically validated before merge.

Sources: .github/workflows/ci.yml:3-9

Job Definition

The workflow defines a single job named test-mosquitto:

The job executes on GitHub's ubuntu-latest runner, which provides Docker pre-installed but requires Docker Compose to be explicitly installed.

Sources: .github/workflows/ci.yml:11-13

Workflow Architecture

Complete Pipeline Flow

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

Step-by-Step Execution

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

Test Steps Detail

Step 1: Repository Checkout

Clones the repository to the runner's workspace using the official GitHub Actions checkout action. This provides access to docker-compose.yml:1-18 and mosquitto.conf:1-7

Sources: .github/workflows/ci.yml:16-17

Step 2: Docker Compose Installation

Installs Docker Compose on the Ubuntu runner. Although Docker Engine is pre-installed on ubuntu-latest, Docker Compose must be explicitly installed through the package manager.

Sources: .github/workflows/ci.yml:19-22

Step 3: Service Startup

Starts only the mosquitto service in detached mode. The -d flag ensures the container runs in the background, allowing the workflow to proceed to health checks. Note that the cloudflared service defined in docker-compose.yml:11-17 is explicitly excluded, as it requires a CLOUDFLARE_TUNNEL_TOKEN which is not available in the CI environment.

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

Step 4: Health Check Loop

The health check implements a retry pattern with the following parameters:

ParameterValueConfiguration
Maximum Attempts10.github/workflows/ci.yml29
Interval Between Attempts10 seconds.github/workflows/ci.yml35
Total Timeout100 secondsCalculated (10 × 10)
Success ConditionContainer status is running.github/workflows/ci.yml30

Health Check Implementation

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

Inspection Command

The health check uses Docker's inspect command with format filtering:

This command:

  1. Queries the container's state using docker inspect
  2. Extracts the Status field from the container state
  3. Checks if the status contains the string running
  4. Returns exit code 0 if found, non-zero otherwise

Sources: .github/workflows/ci.yml30

Step 5: Service Teardown

Executes regardless of health check success or failure. Stops and removes the mosquitto container, ensuring clean resource management. The workflow does not use conditional execution (e.g., if: always()) because Docker Compose steps default to always running.

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

What Is Tested

The CI pipeline validates the following aspects:

AspectValidation MethodCoverage
Container Image Availabilitydocker-compose up attempts to pull eclipse-mosquitto:latestVerifies Docker Hub connectivity and image existence
Container CreationDocker Compose service instantiationValidates service definition in docker-compose.yml:4-9
Configuration ValidityContainer starts without errorsEnsures mosquitto.conf:1-7 is syntactically valid
Volume MountImplicit test during container startupVerifies ./mosquitto.conf mount in docker-compose.yml8
Process StartupContainer reaches running stateConfirms Mosquitto broker process initializes

Sources: .github/workflows/ci.yml:1-43 docker-compose.yml:4-9 mosquitto.conf:1-7

What Is NOT Tested

The following aspects are explicitly excluded from CI testing:

Cloudflared Service

The cloudflared service requires a valid CLOUDFLARE_TUNNEL_TOKEN from docker-compose.yml17 and .env.sample:1-6 This token:

  • Authenticates the tunnel client with Cloudflare's network
  • Is environment-specific and cannot be shared across deployments
  • Would expose the tunnel to public access if leaked in CI logs

Sources: docker-compose.yml:11-17 .github/workflows/ci.yml:24-25

Functional Testing

The pipeline does not perform:

Excluded TestReason
MQTT Protocol ConnectivityNo client connection attempts to ports 1883 or 9001
Message Publishing/SubscribingRequires MQTT client integration
WebSocket ProtocolNo WebSocket connection tests
Network ReachabilityContainers not accessible from outside GitHub Actions network
Performance/Load TestingNot within scope of basic health check
Authentication/AuthorizationCurrent configuration uses anonymous access

Sources: .github/workflows/ci.yml:1-43 mosquitto.conf:1-7

Success and Failure Scenarios

Success Criteria

The workflow succeeds when:

docker inspect --format='{{.State.Status}}' mosquitto
# Returns: running
# Within 10 attempts (100 seconds)

This indicates the mosquitto container:

  1. Started successfully using the configuration from mosquitto.conf:1-7
  2. Loaded the volume mount from docker-compose.yml8
  3. Initialized the Mosquitto broker process
  4. Reached a stable running state

Exit Code : 0 (.github/workflows/ci.yml32)

Sources: .github/workflows/ci.yml:29-32

Failure Scenarios

The workflow fails in the following conditions:

Timeout Failure

Waiting for Mosquitto to be healthy...
(Repeats 10 times)
Mosquitto did not become healthy in time

Exit Code : 1 (.github/workflows/ci.yml39)

Potential Causes :

  • Configuration error in mosquitto.conf:1-7
  • Missing or invalid volume mount in docker-compose.yml8
  • Resource constraints on the GitHub Actions runner
  • Network issues preventing image pull from Docker Hub

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

Docker Compose Failures

Failures during docker-compose up step prevent health checks from running:

Failure TypeManifestationCommon Cause
Image Pull FailureError pulling eclipse-mosquitto:latestDocker Hub connectivity or rate limiting
Syntax ErrorYAML parsing errorInvalid docker-compose.yml:1-18 syntax
Volume Mount ErrorCannot mount ./mosquitto.confFile not found in repository
Port ConflictAddress already in useUnlikely in isolated runner environment

Sources: docker-compose.yml:1-18 .github/workflows/ci.yml:24-25

Running Tests Locally

Developers can replicate the CI environment locally using the same commands:

Note : Local testing may require Docker and Docker Compose to be installed. See Prerequisites for installation instructions.

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

Debugging Failed Workflows

When a workflow fails in GitHub Actions:

graph TB
    Failure["Workflow Failure Detected"]
CheckLogs["Review GitHub Actions logs"]
IdentifyStep{"Which step failed?"}
CheckoutFail["Checkout Step\n[ci.yml:16-17]"]
ComposeFail["Docker Compose Install\n[ci.yml:19-22]"]
StartupFail["Service Startup\n[ci.yml:24-25]"]
HealthFail["Health Check Timeout\n[ci.yml:27-39]"]
CheckRepo["Verify repository permissions"]
CheckRunner["Check runner environment"]
CheckImage["Verify docker-compose.yml syntax\nCheck image availability"]
CheckConfig["Review mosquitto.conf\nCheck container logs"]
Failure --> CheckLogs
 
   CheckLogs --> IdentifyStep
    
 
   IdentifyStep -->|Step 1| CheckoutFail
 
   IdentifyStep -->|Step 2| ComposeFail
 
   IdentifyStep -->|Step 3| StartupFail
 
   IdentifyStep -->|Step 4| HealthFail
    
 
   CheckoutFail --> CheckRepo
 
   ComposeFail --> CheckRunner
 
   StartupFail --> CheckImage
 
   HealthFail --> CheckConfig

Viewing Logs

  1. Navigate to the Actions tab in the GitHub repository
  2. Select the failed workflow run
  3. Click on the test-mosquitto job
  4. Expand the failing step to view output

Common Debugging Steps

Sources: .github/workflows/ci.yml:1-43

Retrieving Container Logs

GitHub Actions does not automatically capture container logs. To debug container-level issues, modify the workflow temporarily:

This step would appear after .github/workflows/ci.yml39 and before .github/workflows/ci.yml:41-42

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

Limitations and Considerations

Scope Limitations

LimitationImpactMitigation
No Cloudflared TestingCannot validate tunnel connectivityManual testing required in deployment environment
No Protocol TestingCannot verify MQTT/WebSocket functionalityConsider integration tests in separate workflow
No Persistence TestingDoes not verify data retentionMosquitto stores no data in default configuration
No Security TestingAnonymous access not validatedCurrent configuration intentionally allows anonymous access

Sources: .github/workflows/ci.yml:1-43 mosquitto.conf:1-7

Runner Environment Differences

The GitHub Actions ubuntu-latest runner may differ from production environments:

  • Architecture : Typically x86_64; production might use ARM
  • Network : Isolated; production might have firewall rules
  • Resources : Limited CPU/memory; production might have different constraints
  • Docker Version : May lag behind latest Docker Engine releases

Sources: .github/workflows/ci.yml13

Performance Considerations

The 100-second timeout .github/workflows/ci.yml:29-39 is conservative but may be insufficient if:

  • Docker Hub implements rate limiting on image pulls
  • The GitHub Actions runner is under heavy load
  • Network latency to Docker Hub is high

In production deployments, containers typically start within 5-10 seconds.

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

Future Enhancements

Potential improvements to the CI pipeline:

Extended Test Coverage

Each enhancement would require additional test steps and potentially separate jobs in .github/workflows/ci.yml:11-42

Sources: .github/workflows/ci.yml:1-43

Integration Testing

For systems deploying the protected features (see Topic Access Control) and Encrypted Retained Messages), additional test jobs could validate:

  • ACL enforcement with test credentials
  • Encrypted volume mounting and data persistence
  • Multi-client pub/sub scenarios

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


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 Practices

Relevant source files

Purpose and Scope

This document explains version control practices for the Docker MQTT Mosquitto Cloudflare Tunnel project, including what files are tracked in Git, what files are explicitly excluded, and best practices for managing secrets and runtime data. This covers the .gitignore configuration and the template-based secret management pattern using .env.sample.

For information about setting up the environment variables themselves, see Environment Variables. For deployment workflows and CI/CD processes, see CI/CD Pipeline.

Version Control Strategy

The project follows a strict separation between configuration templates (which are version controlled) and sensitive data or runtime artifacts (which are not). This ensures that the repository can be safely shared publicly while protecting secrets and avoiding repository bloat from generated files.

Version Controlled Files

The following categories of files are tracked in Git:

CategoryFilesPurpose
Orchestrationdocker-compose.ymlService definitions and container orchestration
Configuration Templatesmosquitto.conf, .env.sampleNon-sensitive configuration and secret templates
DocumentationREADME.md, LICENSEProject documentation and licensing
CI/CD.github/workflows/ci.ymlAutomated testing configuration
Version Control Config.gitignoreExclusion rules for sensitive/generated files

These files define the system's structure and behavior without containing sensitive credentials or environment-specific data.

Sources: .gitignore:1-3 README.md, docker-compose.yml, mosquitto.conf, .env.sample

Excluded Files

The .gitignore:1-3 file explicitly excludes two categories of files:

.env
data/*

These exclusions serve distinct purposes:

Excluded PatternPurposeRationale
.envSensitive credentialsContains CLOUDFLARE_TUNNEL_TOKEN and other secrets
data/*Runtime artifactsGenerated data, persistence files, and logs

Sources: .gitignore:1-3

The .gitignore Configuration

Diagram: Version Control Boundaries Defined by .gitignore

The .gitignore file is minimal but critical, containing only two rules that protect the repository from two distinct risks: credential exposure and runtime data pollution.

Sources: .gitignore:1-3

Rule 1: Excluding .env (Line 1)

The first exclusion rule .gitignore1 prevents the .env file from being committed. This file contains the CLOUDFLARE_TUNNEL_TOKEN required by the cloudflared service to authenticate with Cloudflare's network.

Security Implications:

  • The tunnel token grants access to route traffic through the configured Cloudflare Tunnel
  • If committed, the token would be permanently in Git history even if later removed
  • Public repositories would expose credentials to anyone who clones the repository
  • Compromised tokens allow unauthorized parties to proxy traffic through the tunnel

Sources: .gitignore1 .env.sample1

Rule 2: Excluding data/* (Line 2)

The second exclusion rule .gitignore2 prevents the data/ directory and all its contents from being tracked. This directory is used by the mosquitto container for runtime data storage.

Rationale:

  • Mosquitto may store persistence files (retained messages, subscription state) in data/
  • Runtime logs and temporary files accumulate during operation
  • These files are environment-specific and vary between deployments
  • Including them would cause constant merge conflicts in collaborative workflows
  • Binary persistence files would unnecessarily increase repository size

Sources: .gitignore2 docker-compose.yml

Secret Management Pattern

Diagram: Secret Management Workflow Using .env.sample Template

The project uses a template-based approach to manage secrets. The .env.sample:1-3 file serves as a documented template that IS version controlled, while the actual .env file with real credentials is never committed.

The .env.sample Template

CLOUDFLARE_TUNNEL_TOKEN=your_token

The .env.sample1 file contains:

  • Variable names required by the system
  • Placeholder values (e.g., your_token) that clearly indicate replacement is needed
  • Comments or documentation (if present) explaining each variable's purpose

Sources: .env.sample:1-3

Developer Workflow

When a developer clones the repository, they must:

  1. Copy the template:

  2. Obtain actual credentials from the Cloudflare Zero Trust dashboard (see Cloudflare Tunnel Configuration)

  3. Replace placeholders in .env with real values:

    CLOUDFLARE_TUNNEL_TOKEN=eyJhIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoifQ...
    
  4. Verify the file is ignored by Git:

This workflow ensures every developer can configure their environment while keeping credentials out of version control.

Sources: .env.sample:1-3 .gitignore1

File Classification Decision Tree

Diagram: Decision Tree for Version Control Classification

This decision tree helps determine whether a file should be committed, excluded, or committed as a template.

Sources: .gitignore:1-3 .env.sample:1-3

Best Practices for Contributors

Adding New Configuration Files

When introducing new configuration files to the project:

  1. Evaluate for secrets: If the file will contain credentials, API keys, or tokens:

    • Create a .sample or .example version with placeholders
    • Commit the template file
    • Add the actual filename to .gitignore:1-3
    • Update documentation to explain required variables
  2. Evaluate for runtime data: If the file is generated during container operation:

    • Add the file or directory pattern to .gitignore:1-3
    • Document in README.md where the file comes from
    • Consider whether users need to back up this data
  3. Configuration without secrets: If the file defines behavior but has no sensitive data:

    • Commit directly to the repository
    • Consider whether different environments need different values (if so, use template pattern)

Sources: .gitignore:1-3 .env.sample:1-3

Verifying .gitignore Effectiveness

Before committing changes, verify that sensitive files are properly excluded:

If .env or data/* files appear in git status, the .gitignore:1-3 rules are not working correctly.

Sources: .gitignore:1-3

Handling Accidentally Committed Secrets

If secrets are accidentally committed:

  1. Do not simply delete the file and commit again (history still contains it)
  2. Immediately rotate the compromised credentials
  3. Remove from history using git filter-branch or BFG Repo-Cleaner
  4. Force push to rewrite remote history (only if repository is not shared)
  5. Update.gitignore:1-3 to prevent recurrence

For public repositories, assume any committed secret is compromised and rotate it immediately.

Sources: .gitignore1 .env.sample1

Git Workflow Integration

Clone and Setup

New contributors follow this workflow:

StepCommandResult
1. Clone repositorygit clone <repo-url>Repository with templates only
2. Copy env templatecp .env.sample .envLocal .env file created
3. Configure secretsEdit .env with real valuesSystem ready to deploy
4. Verify exclusiongit status.env should not appear

Sources: .env.sample:1-3 .gitignore1

Pull Requests and Code Review

When reviewing pull requests:

  • Verify no .env files are included in the diff
  • Check that new configuration files follow the template pattern
  • Ensure .gitignore:1-3 is updated if new excludable patterns are introduced
  • Confirm documentation is updated for new required environment variables

Sources: .gitignore:1-3

Branch Strategy

The main branch contains:

  • All version-controlled configuration files
  • The .gitignore exclusion rules
  • Template files like .env.sample

Developers should never commit:

  • The actual .env file (excluded by .gitignore1)
  • Contents of the data/ directory (excluded by .gitignore2)
  • Any other secrets or credentials

Sources: .gitignore:1-3

Relationship to CI/CD

The GitHub Actions CI workflow (see CI/CD Pipeline) runs without access to the .env file because:

  1. The CI environment does not require CLOUDFLARE_TUNNEL_TOKEN (tests only verify Mosquitto startup)
  2. The cloudflared service is not started during CI tests
  3. The docker-compose.yml is structured to allow testing individual services

This demonstrates proper separation of concerns: version-controlled configuration can be tested without requiring secrets.

Sources: .gitignore:1-3 .github/workflows/ci.yml

Summary

The version control strategy for this project achieves three goals:

  1. Security: Secrets remain local and never enter Git history (enforced by .gitignore1)
  2. Clarity: Templates provide clear documentation of required configuration (via .env.sample1)
  3. Clean repository: Runtime artifacts don't pollute version control (excluded by .gitignore2)

This approach enables safe public repository sharing while maintaining functional deployment instructions for all contributors.

Sources: .gitignore:1-3 .env.sample:1-3


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

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:


GitHub

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

The source code for this project is available on GitHub.

Topic Access Control (ACL)

Relevant source files

Purpose and Scope

This document explains the Access Control List (ACL) functionality available in the protected-no-wildcard branch of this repository. The main branch deploys Mosquitto with anonymous access enabled and no topic restrictions. For production environments requiring user-based topic isolation and access control, the protected-no-wildcard branch provides ACL-based authorization.

For information about the anonymous access configuration in the main branch, see Anonymous Access. For encryption of retained messages, which is also available in the protected-no-wildcard branch, see Encrypted Retained Messages.

Sources : README.md:5-11

Overview

Mosquitto supports Access Control Lists (ACLs) that define which users can publish or subscribe to specific topics. The main branch of this repository uses allow_anonymous true in mosquitto.conf2 permitting unrestricted access to all MQTT topics. This configuration is suitable for trusted environments or development scenarios.

The protected-no-wildcard branch implements a user-based topic hierarchy where the first level of each topic path represents the username, enforced through an ACL file located at mosquitto/aclfile.

Sources : mosquitto.conf:1-6 README.md:5-11

Branch Comparison

Diagram: Configuration Differences Between Branches

FeatureMain BranchProtected-No-Wildcard Branch
Anonymous AccessEnabled (allow_anonymous true)Disabled
ACL FileNot presentmosquitto/aclfile
User AuthenticationNot requiredRequired
Topic RestrictionsNoneUsername-prefixed topics
Wildcard SearchesUnrestrictedRestricted to user's own topics

Sources : README.md:5-11 mosquitto.conf2

Accessing the Protected-No-Wildcard Branch

The protected-no-wildcard branch is available as a reference implementation for ACL-based access control. You can view the branch or compare it with the main branch:

  • Branch URL: https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/tree/protected-no-wildcard
  • Diff URL: https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/compare/main...protected-no-wildcard

To switch to this branch locally:

Sources : README.md11

ACL Pattern Architecture

The protected-no-wildcard branch implements a hierarchical topic namespace where each user operates within a topic prefix matching their username. This pattern prevents users from accessing topics belonging to other users while allowing full control within their own namespace.

Diagram: Topic Namespace Isolation

graph TB
    subgraph "Topic Hierarchy"
        root["MQTT Topic Root"]
user1_ns["alice/"]
user2_ns["bob/"]
user3_ns["charlie/"]
user1_devices["alice/devices/+"]
user1_sensors["alice/sensors/+"]
user1_commands["alice/commands/+"]
user2_devices["bob/devices/+"]
user2_sensors["bob/sensors/+"]
end
    
    subgraph "ACL Rules"
        acl_file["mosquitto/aclfile"]
rule_alice["user alice\ntopic alice/#"]
rule_bob["user bob\ntopic bob/#"]
rule_charlie["user charlie\ntopic charlie/#"]
end
    
 
   root --> user1_ns
 
   root --> user2_ns
 
   root --> user3_ns
    
 
   user1_ns --> user1_devices
 
   user1_ns --> user1_sensors
 
   user1_ns --> user1_commands
    
 
   user2_ns --> user2_devices
 
   user2_ns --> user2_sensors
    
 
   acl_file --> rule_alice
 
   acl_file --> rule_bob
 
   acl_file --> rule_charlie
    
 
   rule_alice -.->|Grants Access| user1_ns
 
   rule_bob -.->|Grants Access| user2_ns
 
   rule_charlie -.->|Grants Access| user3_ns

Sources : README.md7

ACL File Structure

The mosquitto/aclfile follows Mosquitto's ACL file format. Each entry defines permissions for a specific user, restricting their access to topics matching a pattern.

Basic ACL Syntax

user <username>
topic [read|write|readwrite] <topic-pattern>

Example ACL Configuration

# User alice can publish and subscribe to all topics under alice/
user alice
topic readwrite alice/#

# User bob can publish and subscribe to all topics under bob/
user bob
topic readwrite bob/#

# User charlie can only subscribe to charlie/sensors/# but publish to charlie/commands/#
user charlie
topic read charlie/sensors/#
topic write charlie/commands/#
ACL DirectiveDescription
user <username>Begins an ACL rule block for the specified username
topic read <pattern>Grants subscribe (read) permission for matching topics
topic write <pattern>Grants publish (write) permission for matching topics
topic readwrite <pattern>Grants both publish and subscribe permissions
# wildcardMatches all remaining topic levels
+ wildcardMatches a single topic level

Sources : README.md7

Access Control Flow

Diagram: ACL Authorization Flow

Sources : README.md7

Wildcard Search Restrictions

The protected-no-wildcard branch implements a "naive" restriction on wildcard searches. With the username-prefixed topic hierarchy, users are prevented from subscribing to wildcards that would cross user boundaries.

Allowed Wildcards

PatternDescriptionAllowed
alice/devices/+Single-level wildcard within user namespace✓ Yes
alice/sensors/#Multi-level wildcard within user namespace✓ Yes
alice/+/temperatureSingle-level wildcard in middle of path✓ Yes

Blocked Wildcards

PatternDescriptionBlocked Reason
#Root-level multi-level wildcardWould access all users' topics
+/sensors/#Wildcard at username levelWould access multiple users' topics
alice/../bob/sensors/#Topic traversal attemptNot valid MQTT topic

Sources : README.md7

Configuration Changes Required

When migrating from the main branch to the protected-no-wildcard branch, several configuration changes are necessary:

mosquitto.conf Changes

Required Additional Files

  1. mosquitto/aclfile : Create this file with appropriate user and topic rules
  2. Password file (if using password authentication): Use mosquitto_passwd utility to create

Docker Compose Volume Mount

The ACL file must be mounted into the mosquitto container:

Sources : README.md7 mosquitto.conf:1-6

Authentication Methods

With allow_anonymous false, Mosquitto requires authentication. The protected-no-wildcard branch supports several authentication mechanisms:

MethodConfigurationUse Case
Password Filepassword_file /mosquitto/config/passwordsSimple username/password authentication
Plugin Authauth_plugin /path/to/plugin.soDatabase-backed authentication
PSKpsk_file /mosquitto/config/psk_filePre-shared key authentication
Certificaterequire_certificate trueTLS client certificate authentication

The specific authentication method used in the protected-no-wildcard branch should be verified by examining the branch's mosquitto.conf.

Sources : mosquitto.conf2

Security Considerations

Diagram: Security Layer Defense in Depth

Benefits of ACL Implementation

  1. Topic Isolation : Users cannot access topics outside their namespace
  2. Wildcard Restriction : Prevents broad topic enumeration by unauthorized users
  3. Granular Control : Different read/write permissions per topic pattern
  4. Audit Trail : Clear definition of who can access what

Limitations

  1. Naive Implementation : The "naive" approach mentioned in the README indicates this is a basic pattern-matching implementation
  2. Topic Format Dependency : Security relies on clients following the username/ topic prefix convention
  3. No Dynamic ACL : Changes to the ACL file require container restart
  4. First-Level Only : Security boundary is only at the first topic level

Production Recommendations

For production deployments requiring robust access control:

  • Implement strict client validation to enforce topic naming conventions
  • Consider additional authentication layers (e.g., OAuth2, JWT)
  • Monitor for topic naming violations
  • Implement rate limiting per user
  • Use TLS client certificates for stronger authentication
  • Enable Mosquitto's logging for audit trails

Sources : README.md7

Migration Checklist

When migrating from the anonymous main branch to the ACL-protected branch:

  • Review the diff between branches: https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/compare/main...protected-no-wildcard
  • Create mosquitto/aclfile with user permissions
  • Update mosquitto.conf to disable anonymous access
  • Update mosquitto.conf to reference the ACL file
  • Create authentication mechanism (password file, plugin, etc.)
  • Update docker-compose.yml to mount ACL file
  • Update existing MQTT clients to use credentials
  • Update client topic subscriptions to use username prefix
  • Test access control with multiple users
  • Verify wildcard restrictions work as expected
  • Update monitoring/alerting for authentication failures

Sources : README.md:5-11

The protected-no-wildcard branch includes additional features beyond ACL:

  • Encrypted Retained Messages : Uses gocryptfs to encrypt retained messages at rest (see Encrypted Retained Messages)
  • Auto-save Retained Messages : Automatically persists retained messages after every message

These features complement the ACL implementation to provide a more secure MQTT broker deployment.

Sources : README.md:8-9


GitHub

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

The source code for this project is available on GitHub.

Encrypted Retained Messages

Relevant source files

Purpose and Scope

This document describes the encrypted retained messages feature available in the protected-no-wildcard branch of the repository. This feature provides transparent encryption of MQTT retained messages using gocryptfs, combined with automatic persistence after each message. This documentation covers the architecture, encryption mechanism, and auto-save functionality for retained messages.

For information about topic-based access control also available in the protected-no-wildcard branch, see Topic Access Control (ACL)). For the base system configuration without these features, see Mosquitto Configuration.

Sources: README.md:5-11


Overview

The protected-no-wildcard branch extends the base system with two key enhancements for retained messages:

  1. Encryption of retained messages using gocryptfs
  2. Automatic persistence after every message

This feature set is designed for scenarios requiring at-rest encryption of MQTT retained messages while maintaining the standard MQTT protocol interface for clients.

Sources: README.md:5-11


MQTT Retained Messages

What Are Retained Messages

Retained messages in MQTT are messages that the broker stores and delivers to new subscribers immediately upon subscription, even if the original publisher is no longer connected. When a client publishes a message with the retain flag set to true, the broker:

  1. Stores the message associated with the topic
  2. Delivers the message to any future subscribers to that topic
  3. Replaces any previously retained message on the same topic

Storage Requirements

By default, Mosquitto stores retained messages in memory and optionally persists them to disk in the data/ directory. The encrypted retained messages feature adds a transparent encryption layer to this persistence mechanism.

Sources: mosquitto.conf:1-6


Encryption Architecture

gocryptfs Overview

The protected-no-wildcard branch uses gocryptfs, a FUSE filesystem that provides transparent encryption. gocryptfs operates at the filesystem layer, meaning:

  • Files written to the mounted directory are automatically encrypted
  • Files read from the mounted directory are automatically decrypted
  • The encryption is transparent to applications (Mosquitto in this case)

Architecture Diagram

Sources: README.md8

Encryption Flow

Sources: README.md8


Auto-Save Mechanism

Persistence Behavior

The protected-no-wildcard branch implements automatic persistence of retained messages after every message. This differs from standard Mosquitto behavior, which may batch persistence operations or persist on a schedule.

Benefits

FeatureStandard MosquittoAuto-Save Implementation
Persistence TimingPeriodic or on shutdownAfter every retained message
Data Loss WindowPotential loss since last saveMinimal (only in-flight messages)
Disk I/OBatched operationsPer-message writes
Recovery GuaranteeLast saved stateMost recent retained messages

Auto-Save Architecture

Sources: README.md9


Integration with Docker Architecture

Volume Mounting

The encrypted retained messages feature requires a gocryptfs mount to be established before Mosquitto starts. This involves:

  1. Creating an encrypted filesystem on the host
  2. Mounting the encrypted filesystem using gocryptfs within the container
  3. Configuring Mosquitto to use the mounted directory for persistence
  4. Ensuring the encryption key is securely managed
graph TB
    subgraph "Container Startup Sequence"
        Start["Container Start"]
InitGocryptfs["Initialize gocryptfs"]
MountEncrypted["Mount Encrypted FS"]
StartMosquitto["Start mosquitto"]
Ready["Ready for Connections"]
end
    
    subgraph "Container Shutdown Sequence"
        Shutdown["Shutdown Signal"]
StopMosquitto["Stop mosquitto"]
FlushData["Flush All Data"]
UnmountFS["Unmount gocryptfs"]
Stop["Container Stop"]
end
    
 
   Start --> InitGocryptfs
 
   InitGocryptfs --> MountEncrypted
 
   MountEncrypted --> StartMosquitto
 
   StartMosquitto --> Ready
    
 
   Shutdown --> StopMosquitto
 
   StopMosquitto --> FlushData
 
   FlushData --> UnmountFS
 
   UnmountFS --> Stop

Container Lifecycle

Sources: README.md:5-11


Security Considerations

Encryption Key Management

The gocryptfs encryption requires a master key to encrypt and decrypt the filesystem. Key management considerations include:

  • Key Storage: The encryption key must be securely stored and accessible to the container
  • Key Rotation: Procedures for rotating encryption keys if compromised
  • Access Control: Limiting which processes can access the encryption key

Threat Model

ThreatProtection ProvidedLimitations
Physical disk theftEncrypted data at restKey must be protected separately
Unauthorized filesystem accessEncrypted files unreadableRequires secure key storage
Memory dumpsData encrypted on diskData decrypted in memory
Network interceptionN/A (handled by Cloudflare Tunnel)Not related to at-rest encryption

Performance Implications

Transparent encryption adds computational overhead:

  • CPU Usage: Encryption/decryption operations consume CPU cycles
  • I/O Latency: Additional layer between application and disk
  • Auto-Save Impact: Per-message persistence increases disk I/O frequency

Sources: README.md8


Accessing the Implementation

Branch Location

The encrypted retained messages feature is implemented in the protected-no-wildcard branch of the repository. To access:

Comparing with Main Branch

To view the complete set of changes required to implement this feature:

https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/compare/main...protected-no-wildcard

This diff shows:

  • gocryptfs integration modifications
  • Mosquitto configuration changes for encrypted persistence
  • Auto-save implementation details
  • Container orchestration updates

Sources: README.md11


Use Cases

Compliance Requirements

Encrypted retained messages are beneficial for:

  • Regulatory Compliance: Meeting data-at-rest encryption requirements (GDPR, HIPAA, etc.)
  • Multi-Tenant Environments: Protecting tenant data from infrastructure administrators
  • Sensitive Data: IoT applications handling personally identifiable information (PII)

Deployment Scenarios

ScenarioEncrypted Messages RecommendedRationale
Public cloud deploymentYesProtect against cloud provider access
Home IoT networkOptionalLower risk environment
Industrial sensorsYesProprietary data protection
Development/testingNoPerformance overhead unnecessary

Sources: README.md:5-11


Relationship to Base System

Differences from Main Branch

The base system (main branch) provides:

  • Unencrypted retained message storage
  • Standard Mosquitto persistence behavior
  • Simpler deployment without encryption overhead

The protected-no-wildcard branch adds:

  • Transparent encryption layer via gocryptfs
  • Guaranteed per-message persistence
  • Additional ACL features (see Topic Access Control (ACL)))

Migration Path

To migrate from the main branch to the encrypted variant:

  1. Back up existing retained messages from the data/ directory
  2. Initialize a gocryptfs encrypted filesystem
  3. Deploy the protected-no-wildcard branch configuration
  4. Manually republish retained messages (encryption is not retroactive)

Sources: README.md:5-11


Summary

The encrypted retained messages feature provides at-rest encryption for MQTT retained messages through gocryptfs integration and automatic per-message persistence. This feature is available in the protected-no-wildcard branch and is designed for deployments requiring data protection compliance or handling sensitive information. The implementation maintains the standard MQTT protocol interface while adding a transparent encryption layer at the filesystem level.

For access control features that complement this encryption, see Topic Access Control (ACL)). For general deployment procedures, see Deployment.

Sources: README.md:5-11


GitHub

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

The source code for this project is available on GitHub.

Production Considerations

Relevant source files

This document addresses critical considerations for deploying the Docker MQTT Mosquitto Cloudflare Tunnel system in production environments. It covers security hardening, authentication, data persistence, monitoring, resource management, and operational best practices.

The current implementation in the main branch prioritizes simplicity and ease of setup. For production deployments, additional configuration and hardening measures are required. For specific access control and encryption implementations, see the [protected-no-wildcard branch](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/8a829fda/protected-no-wildcard branch) mentioned in Advanced Topics.

Security Hardening

Current Security Posture

The default configuration enables anonymous access to the MQTT broker without authentication:

mosquitto.conf2

This configuration allows any client reaching the broker through the Cloudflare Tunnel to publish and subscribe to any topic without credentials. While Cloudflare's network provides protection against network-level attacks (DDoS, direct IP exposure), the broker itself has no application-level access controls.

Production Security Diagram

graph TB
    subgraph "External_Access"
        Client["MQTT Client"]
end
    
    subgraph "Cloudflare_Layer"
        CFEdge["Cloudflare Edge\nDDoS Protection"]
CFTunnel["Cloudflare Tunnel\nEncrypted Transport"]
ZeroTrust["Zero Trust Access\nOptional Policy Enforcement"]
end
    
    subgraph "Docker_Host"
        subgraph "cloudflared_container"
            CFDaemon["cloudflared service"]
TokenAuth["CLOUDFLARE_TUNNEL_TOKEN\nAuthentication"]
end
        
        subgraph "mosquitto_container"
            Broker["mosquitto broker"]
AnonymousAccess["allow_anonymous true\nLine 2"]
NoACL["No ACL configured"]
NoPassword["No password_file"]
end
    end
    
 
   Client --> CFEdge
 
   CFEdge --> ZeroTrust
 
   ZeroTrust --> CFTunnel
 
   CFTunnel --> CFDaemon
 
   TokenAuth -.->|Authenticates| CFDaemon
 
   CFDaemon -->|Proxy to port 9001| Broker
    
 
   AnonymousAccess -.->|Current Config| Broker
 
   NoACL -.->|Production Risk| Broker
 
   NoPassword -.->|Production Risk| Broker

Sources: mosquitto.conf:1-6 docker-compose.yml:11-17 README.md:1-73

Authentication Implementation

For production environments, disable anonymous access and implement authentication. Modify mosquitto.conf2 to include:

Configuration SettingPurposeImplementation
allow_anonymous falseDisable anonymous connectionsReplace line 2 in mosquitto.conf
password_file /mosquitto/config/passwordfileEnable username/password authenticationAdd volume mount in docker-compose.yml
acl_file /mosquitto/config/aclfileImplement topic-level access controlMount ACL configuration file

Password File Creation

The eclipse-mosquitto image includes the mosquitto_passwd utility for creating password files:

This file must be volume-mounted into the container at /mosquitto/config/passwordfile.

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

Topic Access Control (ACL)

The [protected-no-wildcard branch](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/8a829fda/protected-no-wildcard branch) demonstrates ACL implementation. An ACL file defines per-user topic access permissions:

user alice
topic readwrite alice/#

user bob
topic read alice/public/#
topic readwrite bob/#

This restricts users to specific topic namespaces, preventing unauthorized access to other users' data.

Sources: README.md:5-11

Cloudflare Zero Trust Policies

Beyond broker-level authentication, Cloudflare Zero Trust Access can enforce additional policies before traffic reaches the tunnel:

  • Email-based authentication
  • IP address restrictions
  • Geographic access controls
  • Device posture requirements

These policies are configured in the Cloudflare Zero Trust dashboard and provide defense-in-depth.

Sources: README.md:27-29

Data Persistence and Backup

Current Persistence Configuration

The current docker-compose.yml:4-9 configuration has no explicit data volume mounts for persistence. The Mosquitto container stores retained messages and subscription data in ephemeral container storage, which is lost when the container is removed.

Data Persistence Architecture

graph TB
    subgraph "Docker_Compose_Current"
        MosqService["mosquitto service"]
ConfigVolume["./mosquitto.conf\nVolume Mount\nLine 8"]
NoDataVolume["No data volume\nProduction Risk"]
end
    
    subgraph "Production_Persistence"
        DataVolume["./data:/mosquitto/data\nRetained Messages"]
LogVolume["./log:/mosquitto/log\nBroker Logs"]
BackupProcess["Backup Strategy\nRequired"]
end
    
    subgraph "Encrypted_Storage"
        GoCryptFS["gocryptfs\nEncrypted Filesystem\nprotected-no-wildcard"]
end
    
 
   ConfigVolume --> MosqService
 
   NoDataVolume -.->|Should Add| DataVolume
 
   NoDataVolume -.->|Should Add| LogVolume
    
 
   DataVolume --> BackupProcess
 
   LogVolume --> BackupProcess
    
 
   GoCryptFS -.->|Optional Enhancement| DataVolume

Sources: docker-compose.yml:7-8 README.md:8-9

Implementing Persistent Storage

Add volume mounts to the mosquitto service definition in docker-compose.yml:4-9:

Volume MountPurposeConsiderations
./data:/mosquitto/dataPersist retained messages and subscriptionsEnsure host directory has appropriate permissions
./log:/mosquitto/logPersist broker logsConfigure log rotation to prevent disk exhaustion

Update mosquitto.conf to enable persistence:

persistence true
persistence_location /mosquitto/data/
autosave_interval 300

The autosave_interval setting controls how frequently the broker writes in-memory state to disk (in seconds).

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

Encrypted Retained Messages

The [protected-no-wildcard branch](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/8a829fda/protected-no-wildcard branch) implements encrypted storage for retained messages using gocryptfs. This provides at-rest encryption for sensitive MQTT data stored on disk.

Implementation requires:

  1. Installing gocryptfs on the Docker host
  2. Creating an encrypted filesystem mounted at the data directory
  3. Configuring automatic unlocking with secure key management

See Encrypted Retained Messages for detailed implementation guidance.

Sources: README.md:8-9

Backup Strategy

Production deployments require automated backup procedures:

Backup TargetFrequencyRetentionImplementation
Mosquitto data directoryDaily30 daysFilesystem backup tools (rsync, restic, etc.)
mosquitto.confOn changeVersion controlGit commit and push
Password and ACL filesOn changeVersion control with encryptiongit-crypt or external secrets management
Cloudflare tunnel configurationN/ACloudflare dashboard maintains configurationDocument tunnel setup procedures

Disaster Recovery Testing

Regularly test restoration procedures:

  1. Stop the mosquitto service
  2. Restore backed-up data directory
  3. Verify container startup with docker compose logs mosquitto
  4. Validate client connectivity

Sources: docker-compose.yml:4-9

Monitoring and Logging

Current Logging Configuration

The default configuration provides minimal logging visibility. Container logs are available via docker compose logs but are not persisted or aggregated.

Production Monitoring Architecture

graph TB
    subgraph "Container_Logs"
        MosqLogs["mosquitto container\nstdout/stderr"]
CFLogs["cloudflared container\nstdout/stderr"]
end
    
    subgraph "Docker_Logging_Driver"
        DefaultDriver["json-file driver\nNo configuration"]
ProductionDriver["json-file with rotation\nor external driver"]
end
    
    subgraph "Log_Aggregation"
        Syslog["Syslog\nCentralized Logging"]
Fluentd["Fluentd\nLog Forwarding"]
CloudWatch["CloudWatch\nAWS Integration"]
end
    
    subgraph "Metrics_Collection"
        MosqMetrics["Mosquitto $SYS Topics\nBroker Statistics"]
PrometheusExporter["mosquitto-exporter\nPrometheus Metrics"]
AlertManager["Alert Manager\nThreshold Alerts"]
end
    
 
   MosqLogs --> DefaultDriver
 
   CFLogs --> DefaultDriver
    
 
   DefaultDriver -.->|Should Configure| ProductionDriver
 
   ProductionDriver --> Syslog
 
   ProductionDriver --> Fluentd
 
   ProductionDriver --> CloudWatch
    
 
   MosqMetrics --> PrometheusExporter
 
   PrometheusExporter --> AlertManager

Sources: docker-compose.yml:1-18

Docker Logging Configuration

Configure log rotation in docker-compose.yml:4-9 to prevent disk exhaustion:

This limits each container to 30MB of log storage (3 files × 10MB).

Alternative Logging Drivers:

DriverUse CaseConfiguration
syslogCentral syslog serverRequires syslog-address option
fluentdLog aggregation pipelineRequires Fluentd server
awslogsAWS CloudWatch integrationRequires AWS credentials

Sources: docker-compose.yml:4-9

Mosquitto Logging Configuration

Add logging directives to mosquitto.conf:1-6:

log_dest file /mosquitto/log/mosquitto.log
log_type all
log_timestamp true
log_timestamp_format %Y-%m-%d %H:%M:%S

This requires a volume mount for /mosquitto/log as described in [Data Persistence](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/8a829fda/Data Persistence)

Sources: mosquitto.conf:1-6

Monitoring Mosquitto Broker Statistics

Mosquitto publishes internal statistics to $SYS topics. Monitor these for operational insights:

TopicMetricThreshold Alert
$SYS/broker/clients/connectedActive client countAlert if exceeds capacity planning
$SYS/broker/load/messages/sent/1minMessage throughputAlert on sudden drops (potential issue)
$SYS/broker/uptimeBroker uptimeAlert on unexpected restarts
$SYS/broker/heap/currentMemory usageAlert if approaching limits

Subscribe to these topics using an MQTT client or integrate with monitoring systems using mosquitto-exporter for Prometheus integration.

Sources: mosquitto.conf:1-6

Resource Management and Performance

Current Resource Configuration

The docker-compose.yml:4-9 configuration has no resource limits defined. Containers can consume unlimited CPU and memory, potentially impacting host system stability.

Resource Constraint Architecture

graph TB
    subgraph "Docker_Compose_Services"
        MosqService["mosquitto service\nNo limits defined"]
CFService["cloudflared service\nNo limits defined"]
end
    
    subgraph "Host_Resources"
        HostCPU["Host CPU\nShared Resource"]
HostMem["Host Memory\nShared Resource"]
HostDisk["Host Disk I/O\nShared Resource"]
end
    
    subgraph "Production_Limits"
        CPULimit["cpus: 2.0\ncpu_shares: 1024"]
MemLimit["mem_limit: 2g\nmem_reservation: 1g"]
IOLimit["blkio_config\nI/O Throttling"]
end
    
 
   MosqService -.->|Unrestricted Access| HostCPU
 
   MosqService -.->|Unrestricted Access| HostMem
 
   CFService -.->|Unrestricted Access| HostCPU
 
   CFService -.->|Unrestricted Access| HostMem
    
 
   CPULimit -.->|Should Apply| MosqService
 
   MemLimit -.->|Should Apply| MosqService
 
   IOLimit -.->|Should Apply| MosqService

Sources: docker-compose.yml:4-9

Implementing Resource Limits

Add resource constraints to both services in docker-compose.yml:4-9:

Resource Sizing Guidelines:

ServiceCPU LimitMemory LimitRationale
mosquitto2.0 CPUs2GBMessage processing and retained message storage
cloudflared1.0 CPU512MBLightweight proxy with minimal processing overhead

Adjust based on observed load. Monitor with docker stats.

Sources: docker-compose.yml:4-9

Performance Tuning

For high-throughput scenarios, tune Mosquitto configuration in mosquitto.conf:1-6:

ParameterDefaultProduction RecommendationPurpose
max_connections-1 (unlimited)Set based on capacityPrevent resource exhaustion
max_inflight_messages20100-1000 for QoS > 0Increase throughput for reliable messaging
max_queued_messages100010000-100000Buffer for slow consumers
max_packet_size0 (unlimited)10485760 (10MB)Prevent oversized packets

Sources: mosquitto.conf:1-6

Connection Limit Configuration

To prevent resource exhaustion attacks, configure connection limits:

max_connections 1000

Set based on expected client count with 20-30% headroom. Monitor $SYS/broker/clients/maximum to track peak usage.

Sources: mosquitto.conf:1-6

High Availability and Scaling

Current Single-Instance Architecture

The current deployment runs single instances of both the mosquitto and cloudflared services as defined in docker-compose.yml:4-17 This creates single points of failure.

High Availability Architecture Options

graph TB
    subgraph "Current_Architecture"
        SingleMosq["mosquitto container\nSingle Instance"]
SingleCF["cloudflared container\nSingle Instance"]
end
    
    subgraph "HA_Option_1_Multiple_Tunnels"
        MosqPrimary["mosquitto primary\nActive Broker"]
CF1["cloudflared tunnel 1"]
CF2["cloudflared tunnel 2"]
CFN["cloudflared tunnel N"]
CF1 --> MosqPrimary
 
       CF2 --> MosqPrimary
 
       CFN --> MosqPrimary
    end
    
    subgraph "HA_Option_2_Clustering"
        MosqBroker1["mosquitto broker 1\nwith Bridge"]
MosqBroker2["mosquitto broker 2\nwith Bridge"]
MosqBroker1 <-->|Bridge Connection| MosqBroker2
    end
    
    subgraph "HA_Option_3_Load_Balancer"
        CFTunnelHA["cloudflared\nMultiple Replicas"]
LoadBalancer["Cloudflare LB\nHealth Checks"]
MosqCluster["mosquitto instances\nShared Storage"]
LoadBalancer --> CFTunnelHA
 
       CFTunnelHA --> MosqCluster
    end

Sources: docker-compose.yml:4-17

Multiple Cloudflare Tunnels

Deploy multiple cloudflared replicas for tunnel redundancy:

Cloudflare automatically load-balances across active tunnel connections. This provides tunnel-level redundancy but not broker-level redundancy.

Sources: docker-compose.yml:11-17

Mosquitto Bridge Configuration

For multi-broker redundancy, configure Mosquitto bridges. Each broker can bridge topics to a central broker or peer-to-peer:

connection bridge-to-primary
address primary-broker.example.com:1883
topic # both 0

This configuration bridges all topics bidirectionally. Bridge connections require network connectivity between brokers, which may require additional Cloudflare Tunnel configuration.

Sources: mosquitto.conf:1-6

Scaling Considerations

Scaling DimensionApproachConsiderations
VerticalIncrease resource limits in docker-compose.ymlLimited by host capacity; simplest approach
Horizontal (Clients)Deploy multiple cloudflared replicasCloudflare handles load distribution
Horizontal (Brokers)Bridge multiple Mosquitto instancesRequires shared storage or message replication
GeographicDeploy instances in multiple regions with bridgesReduces latency for global clients

Sources: docker-compose.yml:4-17

Network Security

Cloudflare Tunnel Security Model

The Cloudflare Tunnel architecture eliminates inbound firewall rules. All traffic flows through the tunnel established by the cloudflared container as configured in docker-compose.yml:11-17

Network Isolation Architecture

Sources: docker-compose.yml:11-17 README.md:15-73

Docker Network Isolation

The current configuration uses Docker's default bridge network. For production, create an isolated network:

The internal: false setting allows the cloudflared service to establish outbound connections to Cloudflare while preventing direct external access to the mosquitto service.

Sources: docker-compose.yml:1-18

Port Exposure

The current docker-compose.yml:4-9 configuration does not expose ports on the host. This is intentional and should be maintained. All MQTT traffic flows through the Cloudflare Tunnel.

Do not add port mappings like:

Direct port exposure bypasses Cloudflare's protection layer.

Sources: docker-compose.yml:4-9

Rate Limiting

Implement application-level rate limiting in mosquitto.conf:1-6 to prevent abuse:

max_publish_rate 100
max_subscribe_rate 100

These settings limit each client to 100 publishes and 100 subscriptions per second. Adjust based on legitimate client behavior.

Sources: mosquitto.conf:1-6

Maintenance and Updates

Current Update Strategy

Both services use the latest tag as specified in docker-compose.yml:5-12:

  • eclipse-mosquitto:latest
  • cloudflare/cloudflared:latest
graph TB
    subgraph "Current_Configuration"
        MosqLatest["mosquitto\neclipse-mosquitto:latest\nLine 5"]
CFLatest["cloudflared\ncloudflare/cloudflared:latest\nLine 12"]
end
    
    subgraph "Production_Versioning"
        MosqPinned["mosquitto\neclipse-mosquitto:2.0.18"]
CFPinned["cloudflared\ncloudflare/cloudflared:2024.1.5"]
end
    
    subgraph "Update_Process"
        TestEnv["Test Environment\nValidate New Version"]
Rollback["Rollback Plan\nPrevious Version Tag"]
UpdateSchedule["Maintenance Window\nScheduled Updates"]
end
    
 
   MosqLatest -.->|Production Risk| MosqPinned
 
   CFLatest -.->|Production Risk| CFPinned
    
 
   MosqPinned --> TestEnv
 
   CFPinned --> TestEnv
    
 
   TestEnv --> UpdateSchedule
 
   UpdateSchedule --> Rollback

This approach automatically pulls the newest versions but provides no version control or rollback capability.

Update Management Architecture

Sources: docker-compose.yml:5-12

Pinning Image Versions

Update docker-compose.yml:5-12 to use specific version tags:

Version Selection Strategy:

ImageVersion SelectionRationale
eclipse-mosquittoUse specific minor version (e.g., 2.0.18)Ensures stable MQTT protocol implementation
cloudflare/cloudflaredUse dated release (e.g., 2024.1.5)Cloudflare releases are date-tagged and stable

Sources: docker-compose.yml:5-12

Update Procedure

Follow this procedure for production updates:

  1. Check Release Notes : Review changelogs for both Mosquitto and cloudflared
  2. Test in Non-Production : Deploy new versions in a test environment
  3. Backup Current State : Backup data directory and configuration files
  4. Schedule Maintenance Window : Notify users of planned downtime
  5. Update docker-compose.yml : Change image tags to new versions
  6. Pull New Images : docker compose pull
  7. Restart Services : docker compose down && docker compose up -d
  8. Verify Operation : Check logs with docker compose logs -f
  9. Monitor Metrics : Watch $SYS topics for anomalies

Rollback Procedure:

If issues arise after update:

  1. Stop services: docker compose down
  2. Revert image tags in docker-compose.yml:5-12 to previous versions
  3. Restore data directory from backup if necessary
  4. Start services: docker compose up -d

Sources: docker-compose.yml:5-12

Automated Update Monitoring

Monitor for new releases using:

  • GitHub watch notifications for mosquitto and cloudflared
  • Docker Hub webhooks for image updates
  • Automated security scanning tools (e.g., Trivy)

Sources: docker-compose.yml:5-12

Cloudflared Auto-Update Disabled

The --no-autoupdate flag in docker-compose.yml13 disables automatic updates for cloudflared:

This ensures version control and prevents unexpected behavior from automatic updates. Maintain this flag in production deployments.

Sources: docker-compose.yml13

graph TB
    subgraph "Test_Environment"
        TestCompose["docker-compose.yml\nTest Configuration"]
TestMosq["mosquitto container\nTest Instance"]
TestCF["cloudflared container\nSeparate Tunnel"]
end
    
    subgraph "Test_Procedures"
        ConnTest["Connection Test\nMQTT Client Connect"]
PubSubTest["Pub/Sub Test\nMessage Exchange"]
LoadTest["Load Test\nMultiple Clients"]
FailoverTest["Failover Test\nContainer Restart"]
end
    
    subgraph "Production_Environment"
        ProdCompose["docker-compose.yml\nProduction Config"]
ProdMosq["mosquitto container\nProduction Instance"]
ProdCF["cloudflared container\nProduction Tunnel"]
end
    
 
   TestCompose --> TestMosq
 
   TestCompose --> TestCF
    
 
   TestMosq --> ConnTest
 
   ConnTest --> PubSubTest
 
   PubSubTest --> LoadTest
 
   LoadTest --> FailoverTest
    
 
   FailoverTest -.->|Validation Pass| ProdCompose
 
   ProdCompose --> ProdMosq
 
   ProdCompose --> ProdCF

Testing and Validation

Pre-Production Testing

Before deploying to production, validate the complete system:

Test Environment Setup Diagram

Sources: docker-compose.yml:1-18

Connection Testing

Test MQTT connectivity using command-line clients:

WebSocket Connection (Port 9001):

Test Scenarios:

Test CaseCommandExpected Result
Subscribe to topicmosquitto_sub -h hostname -t test/#Connection established
Publish messagemosquitto_pub -h hostname -t test/msg -m "test"Message received by subscribers
Retained messagemosquitto_pub -h hostname -t test/retained -m "data" -rMessage persisted and delivered to new subscribers
QoS 1 deliverymosquitto_pub -h hostname -t test/qos1 -m "data" -q 1PUBACK received
QoS 2 deliverymosquitto_pub -h hostname -t test/qos2 -m "data" -q 2PUBREC/PUBREL/PUBCOMP exchange

Sources: mosquitto.conf:4-5 README.md:59-64

Load Testing

Use tools like mqtt-stresser or [JMeter MQTT Plugin](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/8a829fda/JMeter MQTT Plugin) to validate performance under load:

Monitor broker metrics during load tests:

  • CPU and memory usage: docker stats mosquitto
  • Connection count: Subscribe to $SYS/broker/clients/connected
  • Message rate: Subscribe to $SYS/broker/load/messages/sent/1min

Sources: docker-compose.yml:4-9

Container Health Checks

Add health checks to docker-compose.yml:4-9 for automated failure detection:

This health check subscribes to a $SYS topic to verify the broker is responding. The start_period allows time for container initialization.

Sources: docker-compose.yml:4-9

Automated Testing with CI

The existing CI workflow in .github/workflows/ci.yml tests Mosquitto startup. Enhance this for production validation:

  1. Test anonymous access is disabled (if authentication is configured)
  2. Test ACL enforcement (if ACL is configured)
  3. Test persistence by publishing retained messages and restarting the container
  4. Test WebSocket connectivity on port 9001

Sources: README.md1

Configuration Management

graph TB
    subgraph "Development"
        EnvSample[".env.sample\nTemplate Only"]
LocalEnv[".env\nActual Secrets"]
GitIgnore[".gitignore\nExcludes .env"]
end
    
    subgraph "Version_Control"
        GitRepo["Git Repository\nNo Secrets"]
end
    
    subgraph "Production_Options"
        DockerSecrets["Docker Secrets\nSwarm Mode"]
VaultIntegration["HashiCorp Vault\nExternal Secrets"]
EnvEncryption["git-crypt\nEncrypted .env"]
CloudProvider["AWS Secrets Manager\nCloud Integration"]
end
    
    subgraph "Runtime"
        ComposeService["docker-compose.yml\nReferences ${VAR}"]
CFContainer["cloudflared container\nReceives Token"]
end
    
 
   EnvSample -.->|Developer Copies| LocalEnv
 
   GitIgnore -.->|Prevents Commit| LocalEnv
 
   LocalEnv -->|Source for compose| ComposeService
    
 
   GitIgnore --> GitRepo
    
 
   DockerSecrets -.->|Alternative| ComposeService
 
   VaultIntegration -.->|Alternative| ComposeService
 
   EnvEncryption -.->|Alternative| GitRepo
 
   CloudProvider -.->|Alternative| ComposeService
    
 
   ComposeService --> CFContainer

Environment Variable Security

The CLOUDFLARE_TUNNEL_TOKEN in docker-compose.yml17 contains sensitive authentication credentials. Current security measures:

Secret Management Architecture

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

Docker Secrets (Swarm Mode)

For Docker Swarm deployments, use Docker secrets instead of environment variables:

Create the secret: echo "your-token" | docker secret create cloudflare_tunnel_token -

Sources: docker-compose.yml:13-17

External Secrets Management

For enterprise deployments, integrate with external secrets management:

SolutionIntegration ApproachUse Case
HashiCorp VaultVault agent sidecar or init containerMulti-service deployments
AWS Secrets ManagerECS task execution roleAWS-hosted deployments
Azure Key VaultManaged identityAzure-hosted deployments
Google Secret ManagerWorkload identityGCP-hosted deployments

Sources: docker-compose.yml:16-17

Configuration Validation

Before deployment, validate configuration files:

Mosquitto Configuration Test:

The -t flag tests the configuration without starting the broker.

Docker Compose Validation:

This validates docker-compose.yml syntax and interpolates environment variables.

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

graph TB
    subgraph "Stateful_Components"
        MosqData["mosquitto data\nRetained Messages"]
MosqConf["mosquitto.conf\nBroker Config"]
PasswordFile["passwordfile\nUser Credentials"]
ACLFile["aclfile\nAccess Control"]
EnvFile[".env\nTunnel Token"]
end
    
    subgraph "Backup_Targets"
        LocalBackup["Local Filesystem\nDaily Snapshots"]
RemoteBackup["Remote Storage\nEncrypted Copy"]
GitBackup["Git Repository\nConfig Files Only"]
end
    
    subgraph "Recovery_Testing"
        RestoreTest["Quarterly Recovery Test"]
DocumentRecovery["Recovery Runbook"]
RTO["Recovery Time Objective"]
RPO["Recovery Point Objective"]
end
    
 
   MosqData --> LocalBackup
 
   MosqConf --> GitBackup
 
   PasswordFile --> RemoteBackup
 
   ACLFile --> GitBackup
 
   EnvFile --> RemoteBackup
    
 
   LocalBackup --> RemoteBackup
    
 
   LocalBackup --> RestoreTest
 
   RemoteBackup --> RestoreTest
 
   RestoreTest --> DocumentRecovery
 
   DocumentRecovery --> RTO
 
   DocumentRecovery --> RPO

Disaster Recovery

Backup Requirements

Production deployments require comprehensive backup procedures covering all stateful components.

Backup Architecture

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

Recovery Time Objective (RTO)

Define acceptable downtime for service restoration:

Severity LevelRTO TargetRequired Preparations
Critical (Data Loss)1 hourAutomated backup restoration scripts
Major (Service Down)4 hoursDocumented recovery procedures
Minor (Degraded)24 hoursManual recovery acceptable

Sources: docker-compose.yml:1-18

Recovery Point Objective (RPO)

Define acceptable data loss:

Data TypeRPO TargetBackup Frequency
Retained messages5 minutesContinuous with autosave_interval 300
Configuration filesOn changeGit commit
User credentialsOn changeBackup before modification

Sources: mosquitto.conf:1-6

Recovery Procedures

Complete System Recovery:

  1. Provision new Docker host
  2. Install Docker Engine and docker-compose
  3. Clone repository: git clone https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel
  4. Restore .env file from secure backup
  5. Restore mosquitto.conf, password file, and ACL file from backup
  6. Restore data directory from most recent backup
  7. Execute docker compose pull to download images
  8. Execute docker compose up -d to start services
  9. Verify operation with connection tests

Data-Only Recovery:

  1. Stop services: docker compose down
  2. Restore data directory from backup
  3. Start services: docker compose up -d
  4. Verify retained messages are accessible

Sources: docker-compose.yml:1-18

Testing Recovery Procedures

Quarterly disaster recovery tests validate recovery procedures:

  1. Document current system state
  2. Destroy test environment
  3. Execute recovery procedures
  4. Validate data integrity
  5. Measure actual RTO and RPO
  6. Update recovery documentation with lessons learned

Sources: docker-compose.yml:1-18

Cost Optimization

Cloudflare Tunnel Costs

Cloudflare Tunnel is available on the Free plan with limitations and paid plans for higher requirements:

PlanCostTunnel LimitUse Case
Free$0/month1 userDevelopment, small deployments
Zero Trust Standard$7/user/monthUp to 50 seatsSmall teams
Zero Trust EnterpriseCustomUnlimitedLarge organizations

For production MQTT deployments with minimal user access requirements, the Free plan may be sufficient if only the tunnel itself needs to be maintained (clients connect to MQTT, not individual users logging in).

Sources: README.md:19-20

Docker Resource Optimization

Right-size resource limits in docker-compose.yml:4-17 based on actual usage:

  1. Deploy with conservative limits
  2. Monitor actual resource consumption with docker stats
  3. Adjust limits to provide 20-30% headroom above peak usage
  4. Iterate monthly as load characteristics change

Resource Monitoring:

Sources: docker-compose.yml:4-17

Data Storage Optimization

Minimize storage costs for retained messages:

max_queued_bytes 10485760

This limits queued message storage to 10MB per client. Adjust based on message patterns.

For long-term message retention, consider:

  • Implementing message TTL policies
  • Archiving old retained messages to cheaper storage tiers
  • Using compression for archived data

Sources: mosquitto.conf:1-6

Compliance and Auditing

Audit Logging

For compliance requirements, enable comprehensive audit logging in mosquitto.conf:1-6:

log_type error
log_type warning
log_type notice
log_type information
log_type subscribe
log_type unsubscribe

This logs all client subscriptions and unsubscriptions for audit trails.

Sources: mosquitto.conf:1-6

Data Retention Policies

Document data retention requirements:

Data TypeRetention PeriodDeletion Method
MQTT broker logs90 daysAutomated log rotation
Retained messagesBased on business requirementsManual or automated cleanup
Audit logs7 years (for some regulations)Archive to cold storage

Sources: mosquitto.conf:1-6

Privacy Considerations

For GDPR or similar privacy regulations:

  1. Implement user data isolation using ACLs (see Topic Access Control))
  2. Document message content and retention in privacy policy
  3. Implement data deletion procedures for retained messages
  4. Encrypt messages at rest (see Encrypted Retained Messages)
  5. Log data access for audit trails

Sources: README.md:5-11


Summary

Production deployment of the Docker MQTT Mosquitto Cloudflare Tunnel system requires significant hardening beyond the simple configuration in the main branch. Key production requirements:

  1. Security: Disable anonymous access (mosquitto.conf2), implement authentication and ACLs
  2. Persistence: Add data volume mounts to docker-compose.yml:4-9 for retained messages
  3. Monitoring: Configure logging, metrics collection, and alerting
  4. Resource Management: Define resource limits in docker-compose.yml:4-9
  5. High Availability: Deploy multiple tunnel replicas or broker instances with bridging
  6. Maintenance: Pin image versions in docker-compose.yml:5-12 and establish update procedures
  7. Testing: Implement health checks and load testing
  8. Disaster Recovery: Automate backups and document recovery procedures

The [protected-no-wildcard branch](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/8a829fda/protected-no-wildcard branch) provides reference implementations for ACL-based access control and encrypted storage.

Sources: README.md:1-73 docker-compose.yml:1-18 mosquitto.conf:1-6

Dismiss

Refresh this wiki

This wiki was recently refreshed. Please wait 1 day to refresh again.

On this page

  • Production Considerations
  • Security Hardening
  • Current Security Posture
  • Authentication Implementation
  • Topic Access Control (ACL)
  • Cloudflare Zero Trust Policies
  • Data Persistence and Backup
  • Current Persistence Configuration
  • Implementing Persistent Storage
  • Encrypted Retained Messages
  • Backup Strategy
  • Monitoring and Logging
  • Current Logging Configuration
  • Docker Logging Configuration
  • Mosquitto Logging Configuration
  • Monitoring Mosquitto Broker Statistics
  • Resource Management and Performance
  • Current Resource Configuration
  • Implementing Resource Limits
  • Performance Tuning
  • Connection Limit Configuration
  • High Availability and Scaling
  • Current Single-Instance Architecture
  • Multiple Cloudflare Tunnels
  • Mosquitto Bridge Configuration
  • Scaling Considerations
  • Network Security
  • Cloudflare Tunnel Security Model
  • Docker Network Isolation
  • Port Exposure
  • Rate Limiting
  • Maintenance and Updates
  • Current Update Strategy
  • Pinning Image Versions
  • Update Procedure
  • Automated Update Monitoring
  • Cloudflared Auto-Update Disabled
  • Testing and Validation
  • Pre-Production Testing
  • Connection Testing
  • Load Testing
  • Container Health Checks
  • Automated Testing with CI
  • Configuration Management
  • Environment Variable Security
  • Docker Secrets (Swarm Mode)
  • External Secrets Management
  • Configuration Validation
  • Disaster Recovery
  • Backup Requirements
  • Recovery Time Objective (RTO)
  • Recovery Point Objective (RPO)
  • Recovery Procedures
  • Testing Recovery Procedures
  • Cost Optimization
  • Cloudflare Tunnel Costs
  • Docker Resource Optimization
  • Data Storage Optimization
  • Compliance and Auditing
  • Audit Logging
  • Data Retention Policies
  • Privacy Considerations
  • Summary

GitHub

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

The source code for this project is available on GitHub.

Reference

Relevant source files

This page provides technical reference documentation for all configuration files and settings in the Docker MQTT Mosquitto Cloudflare Tunnel system. It serves as a comprehensive guide to the structure and relationships between configuration files.

For detailed references of individual configuration files, see:

For security and access control details, see Security Model. For production deployment considerations, see Production Considerations.

Configuration Files Overview

The system uses three primary configuration files to define services, MQTT broker behavior, and authentication credentials:

FilePurposeVersion ControlledContains Secrets
docker-compose.ymlService orchestration and container definitionsYesNo
mosquitto.confMQTT broker configuration (listeners, protocols, access control)YesNo
.envRuntime secrets and environment variablesNoYes
.env.sampleTemplate for .env fileYesNo

Configuration File Relationships

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

Configuration Directive Mapping

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

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

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

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

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 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

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

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"]

.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


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 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 Docker Compose Orchestration. For deployment procedures, see Deployment. For environment variable setup, see Environment Variables Reference.

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


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 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


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 Reference

Relevant source files

This document provides a complete technical reference for all environment variables used in the Docker MQTT Mosquitto Cloudflare Tunnel system. It describes each variable's purpose, format, where it is used, and its security implications.

For instructions on creating and configuring your .env file, see Environment Variables. For obtaining the Cloudflare tunnel token value, see Cloudflare Tunnel Configuration. For security considerations regarding environment variable management, see Security Model.


Environment Variable Loading

The system uses environment variables to inject sensitive configuration data into containers at runtime without storing credentials in version-controlled files. Environment variables are loaded from a .env file in the repository root and made available to Docker Compose services.

graph LR
    EnvSample[".env.sample\n(Version Controlled)"]
EnvFile[".env\n(Not Version Controlled)"]
GitIgnore[".gitignore"]
ComposeFile["docker-compose.yml"]
CloudflaredService["cloudflared container"]
EnvSample -->|Template for| EnvFile
 
   GitIgnore -->|Excludes| EnvFile
 
   EnvFile -->|Read by| ComposeFile
 
   ComposeFile -->|Passes to| CloudflaredService

Variable Loading Flow

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

Docker Compose Integration

The docker-compose.yml file references environment variables using the ${VARIABLE_NAME} syntax and explicitly declares them in the environment section of services that require them.

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

graph TB
    subgraph "Repository"
        EnvSample[".env.sample\nLine 1: CLOUDFLARE_TUNNEL_TOKEN"]
EnvActual[".env\nContains actual token"]
end
    
    subgraph "docker-compose.yml"
        CommandLine["Line 14: command with ${CLOUDFLARE_TUNNEL_TOKEN}"]
EnvSection["Line 16-17: environment section"]
end
    
    subgraph "Runtime"
        CloudflaredCmd["cloudflared process\ntunnel run --token <value>"]
end
    
 
   EnvSample -.->|Developer copies| EnvActual
 
   EnvActual -->|Docker Compose reads| CommandLine
 
   EnvActual -->|Docker Compose reads| EnvSection
 
   CommandLine -->|Substituted in| CloudflaredCmd
 
   EnvSection -->|Available as env var| CloudflaredCmd

Environment Variables Reference Table

Variable NameRequiredUsed ByPurposeFormat
CLOUDFLARE_TUNNEL_TOKENYescloudflared serviceAuthenticates the cloudflared tunnel client with Cloudflare's networkBase64-encoded JSON token string

Variable Details

CLOUDFLARE_TUNNEL_TOKEN

Type: String (Base64-encoded JSON)
Required: Yes
Used By: cloudflared container
Defined In: docker-compose.yml14 docker-compose.yml17

Purpose

The CLOUDFLARE_TUNNEL_TOKEN is a cryptographic credential that authenticates the cloudflared container with Cloudflare's edge network. This token establishes trust and authorization for the tunnel to connect, enabling secure traffic routing from Cloudflare's infrastructure to the local mosquitto broker.

Usage Locations

The variable is used in two locations within docker-compose.yml:

  1. Command Line Substitution (docker-compose.yml14): The token is interpolated directly into the cloudflared command:

    command: tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}
    
  2. Environment Variable Declaration (docker-compose.yml:16-17): The variable is also declared in the service's environment section, making it available to the container's environment:

Format and Structure

The token is a long string that encodes tunnel configuration and authentication credentials. It typically:

  • Contains base64-encoded JSON data
  • Includes tunnel ID, account credentials, and routing information
  • Length varies but typically exceeds 200 characters
  • Does not contain spaces or newlines

Example template from .env.sample1:

CLOUDFLARE_TUNNEL_TOKEN=your_token

How to Obtain

The token is generated by Cloudflare when creating a tunnel through the Zero Trust dashboard. See Cloudflare Tunnel Configuration for detailed instructions on obtaining this value.

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

Security Considerations

The CLOUDFLARE_TUNNEL_TOKEN is a sensitive credential that must be protected:

  • Never commit to version control : The .env file containing actual tokens is excluded by .gitignore
  • Access control : Only authorized users should have access to the .env file on the Docker host
  • Rotation : If compromised, regenerate the tunnel and token through the Cloudflare dashboard
  • Scope : The token grants access to route traffic through the tunnel; protect it with the same rigor as API keys

Validation

The system does not perform local validation of the token format. Validation occurs when the cloudflared container attempts to connect to Cloudflare's network:

  • Valid token : Container establishes tunnel connection and enters running state
  • Invalid token : Container fails to authenticate and may restart repeatedly (depending on restart policy)
  • Missing token : Docker Compose will pass an empty string, causing immediate authentication failure

Check container logs for authentication errors:

Troubleshooting

Common issues related to CLOUDFLARE_TUNNEL_TOKEN:

SymptomLikely CauseSolution
Container restarts continuouslyInvalid or expired tokenVerify token in Cloudflare dashboard, regenerate if needed
Error: "unable to authenticate"Token not loaded from .envVerify .env file exists in repository root and contains token
Error: "tunnel credentials file not found"Token format incorrectEnsure token is copied completely without line breaks or extra spaces
Empty token error.env file not in correct locationEnsure .env is in same directory as docker-compose.yml

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


Environment Variable Lifecycle

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


Loading Mechanism Details

Docker Compose Variable Resolution

Docker Compose resolves environment variables through a precedence hierarchy:

  1. Shell environment variables (highest precedence)
  2. .env file in project directory
  3. environment section in docker-compose.yml
  4. Default values in variable substitution syntax (not used in this project)

The system relies on the .env file method, which is automatically loaded by Docker Compose when present in the same directory as docker-compose.yml.

Variable Substitution Syntax

The ${VARIABLE_NAME} syntax in docker-compose.yml14 triggers Docker Compose to:

  1. Read the .env file
  2. Find the line CLOUDFLARE_TUNNEL_TOKEN=<value>
  3. Extract the value after the = sign
  4. Substitute it into the command string

Environment Variable Export

The environment section in docker-compose.yml:16-17 uses shorthand syntax:

This shorthand means: "Export the environment variable named CLOUDFLARE_TUNNEL_TOKEN to the container, using the value from the host environment (which Docker Compose loaded from .env)."

Sources: docker-compose.yml:14-17 .env.sample:1-3


No Other Environment Variables

This system does not use any other environment variables beyond CLOUDFLARE_TUNNEL_TOKEN. Other configuration is managed through:

  • mosquitto.conf : Mosquitto broker settings (mosquitto.conf)
  • docker-compose.yml : Container orchestration settings (docker-compose.yml:1-18)
  • Cloudflare Dashboard : Tunnel routing and public hostname configuration

If you need to add custom environment variables for extensions or modifications, follow the same pattern:

  1. Add the variable to .env.sample as a template
  2. Add the actual value to .env (which remains untracked)
  3. Reference it in docker-compose.yml using ${VARIABLE_NAME} syntax
  4. Declare it in the service's environment section if the container needs it as an environment variable

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


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


GitHub

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

The source code for this project is available on GitHub.

Troubleshooting

Relevant source files

This page provides diagnostic procedures and solutions for common issues encountered when deploying and operating the Docker MQTT Mosquitto Cloudflare Tunnel system. It covers problems related to container startup, tunnel connectivity, MQTT client connections, and configuration errors.

For production deployment considerations and hardening, see Production Considerations. For security-related configuration, see Security Model.

Diagnostic Overview

The following decision tree helps identify the component experiencing issues:

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

Container Verification

Checking Container Status

First, verify that both the mosquitto and cloudflared containers are running:

Expected output should show both containers with status "Up":

CONTAINER IDIMAGECOMMANDSTATUSNAMES
...eclipse-mosquitto:latest...Up X minutesmosquitto
...cloudflare/cloudflared:latest...Up X minutescloudflared

If containers are missing or have status "Exited" or "Restarting", proceed to the relevant troubleshooting section below.

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

graph LR
 
   DockerPS["docker ps"] --> CheckMosquitto{"mosquitto\ncontainer visible?"}
DockerPS --> CheckCF{"cloudflared\ncontainer visible?"}
CheckMosquitto -->|No| MosquittoDown["Mosquitto stopped\nor failed"]
CheckMosquitto -->|Yes| MosquittoStatus["Check Status field"]
CheckCF -->|No| CFDown["Cloudflared stopped\nor failed"]
CheckCF -->|Yes| CFStatus["Check Status field"]
MosquittoStatus --> StatusUp{"Status: Up?"}
StatusUp -->|No| MosquittoRestarting["Container restarting\nCheck logs"]
StatusUp -->|Yes| MosquittoOK["Mosquitto running"]
CFStatus --> CFStatusUp{"Status: Up?"}
CFStatusUp -->|No| CFRestarting["Container restarting\nCheck token"]
CFStatusUp -->|Yes| CFOK["Cloudflared running"]

Docker Compose Startup Failures

Issue: Containers Fail to Start

Symptoms :

  • Running docker compose up results in immediate exit
  • Error messages about missing files or invalid configuration
  • Containers repeatedly restart

Common Causes :

Error PatternCauseSolution
no configuration file providedmosquitto.conf not foundVerify mosquitto.conf:1-6 exists in project root
invalid or missing environment variable.env file missing or malformedCreate .env from .env.sample, ensure CLOUDFLARE_TUNNEL_TOKEN is set
Error response from daemon: pull access deniedDocker image unavailableCheck internet connectivity, verify image names in docker-compose.yml:5-12
Bind mount failedVolume path incorrectVerify volume path in docker-compose.yml:7-8 matches file location

Mosquitto Container Exits Immediately

Diagnostic Steps :

  1. Check mosquitto logs:

  2. Common error patterns:

Configuration File Syntax Errors :

The mosquitto.conf file must have valid syntax. Each listener directive must be complete:

listener <port>
[allow_anonymous true|false]
[protocol websockets|mqtt]

Verify the configuration at mosquitto.conf:1-6 matches the expected format:

  • Line 1: listener 1883 (standard MQTT)
  • Line 2: allow_anonymous true
  • Line 4: listener 9001 (WebSocket)
  • Line 5: protocol websockets

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

Cloudflared Container Exits Immediately

Diagnostic Steps :

  1. Check cloudflared logs:

  2. Identify error type:

Log MessageIssueSolution
failed to get tunnelInvalid or missing CLOUDFLARE_TUNNEL_TOKENVerify token in .env file matches Cloudflare dashboard
failed to dial Cloudflare edgeNetwork connectivity issueCheck internet connection, firewall rules
unauthorized: authentication failedToken expired or revokedGenerate new token in Cloudflare Zero Trust dashboard
no such hostDNS resolution failureCheck DNS configuration, network settings

Token Configuration Issues :

The environment variable must be correctly passed from .env to the container. Verify:

  1. .env file exists in project root with format:
CLOUDFLARE_TUNNEL_TOKEN=your_actual_token_here
  1. The token is referenced in docker-compose.yml14 as ${CLOUDFLARE_TUNNEL_TOKEN}
  2. The environment variable is declared in docker-compose.yml:16-17

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

Cloudflared Tunnel Issues

Tunnel Connection Failures

Symptoms :

  • cloudflared container runs but clients cannot connect
  • Logs show repeated connection attempts
  • Cloudflare dashboard shows tunnel as "Inactive"

Diagnostic Commands :

Common Issues :

  1. Token Mismatch : The token in .env doesn't match the tunnel configuration

    • Solution: Copy exact token from Cloudflare dashboard as shown in README.md:47-51
    • Recreate .env file with correct format
  2. Network Isolation : Docker container cannot reach Cloudflare edge servers

    • Solution: Verify Docker networking, check corporate firewall/proxy settings
    • Test connectivity: docker exec cloudflared ping cloudflare.com
  3. Tunnel Deleted in Dashboard : The tunnel was deleted but token still in use

    • Solution: Create new tunnel in Cloudflare Zero Trust, update token in .env

Sources : docker-compose.yml:11-17, README.md:23-54

Public Hostname Routing Issues

Symptoms :

  • Tunnel shows "Active" in Cloudflare dashboard
  • MQTT clients receive connection refused or timeout errors
  • No traffic reaches mosquitto container

Verification Steps :

  1. Check public hostname configuration in Cloudflare dashboard:

    • Navigate to: Zero Trust > Networks > Tunnels > [Your Tunnel] > Public Hostname
    • Verify service type is "HTTP" (see README.md:61)
    • Verify URL points to mosquitto:9001 (see README.md:62)
  2. Test internal routing:

Common Configuration Errors :

Dashboard SettingIncorrect ValueCorrect ValueImpact
Service TypeTCP/SSH/RDPHTTPProtocol mismatch prevents connection
URLmosquitto:1883mosquitto:9001Wrong port; port 9001 is WebSocket listener
URLlocalhost:9001mosquitto:9001DNS resolution fails; use container name
ProtocolHTTPSHTTPUnnecessary TLS causes handshake failure

Docker Network Resolution :

The hostname mosquitto in the URL resolves via Docker's internal DNS to the container_name specified in docker-compose.yml6 If the container name is changed, update the Cloudflare public hostname URL accordingly.

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

MQTT Client Connection Issues

Connection Refused or Timeout

Client Configuration Requirements :

For successful connection through Cloudflare Tunnel, clients must:

  1. Use the public hostname configured in Cloudflare Zero Trust (e.g., mqtt.example.com)
  2. Use WebSocket protocol when connecting through the tunnel
  3. Use standard port (80 for HTTP, 443 for HTTPS) - do not specify 9001
  4. Not require TLS at the MQTT level (Cloudflare provides TLS termination)

Example Client Configurations :

Client TypeConnection StringProtocolNotes
Mosquitto CLImosquitto_pub -h mqtt.example.com -t testMQTT over TCPWill fail; use WebSocket client instead
JavaScript MQTT.jsws://mqtt.example.com or wss://mqtt.example.comWebSocketRecommended
Python Paho MQTTTransport: websocketsWebSocketSet transport explicitly
Mobile AppsDepends on libraryWebSocketCheck library supports WebSocket transport

Common Mistakes :

  1. Specifying Port 9001 : Clients should not include :9001 in the hostname. Cloudflare listens on standard ports and routes internally.

  2. Using Standard MQTT Instead of WebSocket : Port 9001 is configured with protocol websockets in mosquitto.conf5 Standard MQTT clients will fail.

  3. Direct Connection Attempts : Clients attempting to connect directly to the Docker host IP will fail. All connections must go through Cloudflare.

Sources : mosquitto.conf:4-5, README.md:59-66

Protocol Mismatch Errors

Symptoms :

  • Client connects but immediately disconnects
  • Error messages about unexpected packet format
  • "Bad request" or "HTTP 400" errors

Root Cause Analysis :

The mosquitto broker has two listeners with different protocols:

Solutions :

  1. For External Clients (through Cloudflare) :

    • Must use WebSocket-capable MQTT libraries
    • Connection URL format: ws:// or wss:// (not mqtt://)
    • The Cloudflare tunnel routes to port 9001 which requires WebSocket protocol
  2. For Internal/Development Testing :

    • Standard MQTT clients can connect directly to port 1883
    • Requires Docker port exposure (not configured by default)
    • Not recommended for production use

Sources : mosquitto.conf:1-5, README.md:62

Anonymous Access Confusion

Symptoms :

  • Clients expecting authentication prompts receive none
  • Security concerns about unauthenticated access
  • Confusion about access control

Current Configuration :

The allow_anonymous true directive at mosquitto.conf2 permits connections without username/password authentication. This is intentional for the base configuration but may not be suitable for all deployments.

Security Implications :

When Anonymous Access is Appropriate :

  • Trusted environment where all clients are controlled
  • Network-level security (Cloudflare Tunnel) provides sufficient protection
  • Simplicity is prioritized over fine-grained access control

When to Require Authentication :

  • Multi-tenant environments
  • Untrusted client devices
  • Compliance requirements for access logging
  • Topic-level access control needed

For authentication and ACL configuration, see the [protected-no-wildcard branch](https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel/blob/8a829fda/protected-no-wildcard branch) or consult Topic Access Control (ACL)).

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

Mosquitto Configuration Issues

Configuration File Syntax Errors

The mosquitto.conf file uses a simple key value format. Common syntax errors include:

ErrorSymptomFix
Missing listener portError: Empty listener statementEnsure listener is followed by port number
Protocol on wrong listenerUnexpected protocol behaviorprotocol websockets must follow listener 9001, not listener 1883
Typo in directiveError: Unknown configuration variableCheck spelling of directives like allow_anonymous
Extra charactersParse errorsNo trailing characters after directive values

Valid Configuration Structure :

listener <port>
[listener-specific options]

listener <port>
[listener-specific options]

Reference the working configuration at mosquitto.conf:1-6

Sources : mosquitto.conf:1-6

Listener Configuration Problems

Issue: Ports Not Available

If mosquitto logs show Error: Address already in use, another service is using port 1883 or 9001.

Diagnostic :

Solutions :

  1. Stop conflicting service
  2. Change mosquitto listener ports in mosquitto.conf
  3. If changing ports, update Cloudflare public hostname URL accordingly

Issue: WebSocket Listener Not Working

If WebSocket clients cannot connect but standard MQTT clients can:

  1. Verify protocol websockets directive exists at mosquitto.conf5
  2. Confirm it's associated with the correct listener (9001)
  3. Check that Cloudflare public hostname routes to port 9001, not 1883

Sources : mosquitto.conf:1-6, README.md:62

Health Check and CI/CD Issues

GitHub Actions CI Failures

The CI pipeline at .github/workflows/ci.yml:1-42 tests mosquitto startup. If CI fails, it indicates a fundamental issue with the configuration.

CI Workflow Troubleshooting :

Common CI Failure Causes :

  1. Invalid mosquitto.conf :

    • Syntax errors prevent broker startup
    • Solution: Test locally with docker compose up mosquitto
  2. Missing mosquitto.conf :

    • File not committed to repository
    • Check .gitignore doesn't exclude it
  3. Port Conflicts in CI Runner :

    • Unlikely but possible on shared runners
    • CI only starts mosquitto, not cloudflared, to avoid needing real tunnel token

Local Replication of CI Test :

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

Health Check Loop Timeout

The CI health check uses a 10-iteration loop with 10-second sleep intervals (.github/workflows/ci.yml:29-39), allowing up to 100 seconds for mosquitto to become healthy.

If Health Check Times Out :

  1. Check Image Pull Time : First run pulls eclipse-mosquitto:latest, which may take time

    • Not typically an issue as GitHub runners have good bandwidth
  2. Check Container Logs :

Look for configuration errors or startup failures

  1. Check System Resources : Insufficient memory/CPU could slow startup

    • Mosquitto is lightweight; this is rarely the issue
  2. Verify Docker Engine : Docker daemon issues prevent container creation

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

Debugging Techniques

Log Analysis

Viewing Real-Time Logs :

Key Log Indicators :

ServiceLog MessageMeaningAction
mosquittoOpening ipv4 listen socket on port 1883Listener 1883 started successfullyNormal
mosquittoOpening websockets listen socket on port 9001Listener 9001 started successfullyNormal
mosquittoError: Unable to open config fileConfig file missing or inaccessibleCheck docker-compose.yml:7-8 volume mount
mosquittoNew connection fromClient connectedNormal
cloudflaredConnection establishedTunnel connected to Cloudflare edgeNormal
cloudflaredfailed to get tunnelToken invalidUpdate .env with correct token
cloudflaredRetrying connectionTemporary connection issueMonitor; if persists, check network

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

Container Inspection

Detailed Container State :

Common Inspection Checks :

  1. Restart Count : High restart count indicates recurring failures

  2. Environment Variables : Verify cloudflared receives token

  3. Network Connectivity : Check both containers are on same network

Sources : docker-compose.yml:1-18

Testing MQTT Connectivity

Internal Testing (Within Docker Network) :

External Testing (Through Cloudflare Tunnel) :

For WebSocket testing, use a WebSocket-capable MQTT client:

Sources : mosquitto.conf:1-6, README.md:59-66

Network Troubleshooting

Docker Network Investigation :

Common Network Issues :

  1. Containers on Different Networks : Should both be on default compose network
  2. DNS Resolution Failure : Container name mosquitto doesn't resolve
  3. Firewall Within Container : Unlikely with standard images

Sources : docker-compose.yml:1-18

Environment Variable Issues

.env File Problems

Verification Checklist :

Common .env Mistakes :

  1. Spaces Around Equals : KEY = value (incorrect) vs KEY=value (correct)
  2. Quotes : Not needed unless value contains spaces
  3. Comments : Use # at start of line, not inline
  4. File Name : Must be exactly .env, not env or .env.txt
  5. Encoding : Must be UTF-8, not UTF-16 or other encodings
  6. Line Endings : Use Unix line endings (LF), not Windows (CRLF)

Verification Command :

Sources : .env.sample, docker-compose.yml:14-17, README.md:51

Token Format Issues

The CLOUDFLARE_TUNNEL_TOKEN is a long base64-encoded string. Common issues:

  1. Truncated Token : Token copied incompletely from dashboard

    • Tokens are typically 200+ characters
    • Verify entire token was copied
  2. Corrupted Token : Extra characters or line breaks inserted

    • Token should be single line with no spaces
    • Common when copying from certain terminals
  3. Wrong Token Type : Using API key instead of tunnel token

    • Token should start with eyJ (base64-encoded JSON)
    • Obtained from specific tunnel creation flow in README.md:47-51

Sources : README.md:47-51, docker-compose.yml:14-17

Additional Resources

For ongoing issues not covered in this troubleshooting guide:

For advanced configuration topics:

Sources : README.md:15-21, README.md:5-11