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
| Service | Container Name | Image | Purpose |
|---|---|---|---|
mosquitto | mosquitto | eclipse-mosquitto:latest | MQTT message broker with two listeners (TCP and WebSocket) |
cloudflared | cloudflared | cloudflare/cloudflared:latest | Cloudflare Tunnel client that proxies traffic to the broker |
Sources : docker-compose.yml:4-17
Configuration Files
| File | Purpose | Version Controlled |
|---|---|---|
docker-compose.yml | Service orchestration and container definitions | Yes |
mosquitto.conf | Mosquitto broker configuration (listeners, protocols, access control) | Yes |
.env | Environment variables including CLOUDFLARE_TUNNEL_TOKEN | No (secret) |
.env.sample | Template for .env file | Yes |
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:
- Image :
eclipse-mosquitto:latestdocker-compose.yml5 - Container Name :
mosquittodocker-compose.yml6 - Configuration :
mosquitto.confis mounted to/mosquitto/config/mosquitto.confdocker-compose.yml:7-8 - Restart Policy :
unless-stoppeddocker-compose.yml9
The broker exposes two listeners defined in mosquitto.conf:1-6:
- Port
1883: Standard MQTT TCP listener mosquitto.conf1 - Port
9001: MQTT over WebSocket listener mosquitto.conf:4-5
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:
- Image :
cloudflare/cloudflared:latestdocker-compose.yml12 - Container Name :
cloudflareddocker-compose.yml13 - Command :
tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}docker-compose.yml14 - Environment Variable :
CLOUDFLARE_TUNNEL_TOKENsourced from.envfile docker-compose.yml:16-17 - Restart Policy :
unless-stoppeddocker-compose.yml15
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
- Tunnel Establishment : The
cloudflaredcontainer connects outbound to Cloudflare using theCLOUDFLARE_TUNNEL_TOKENfrom.envdocker-compose.yml:16-17 - Client Connection : External MQTT clients connect to the Cloudflare public hostname configured in the Zero Trust dashboard README.md:59-64
- Proxy Routing : Cloudflare routes the traffic through the encrypted tunnel to the
cloudflaredcontainer - Internal Forwarding : The
cloudflaredcontainer proxies traffic tomosquitto:9001README.md62 - MQTT Processing : The
mosquittocontainer 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 Path | Purpose | Notes |
|---|---|---|
README.md | Setup guide and documentation | Includes Cloudflare dashboard walkthrough |
docker-compose.yml | Service definitions for mosquitto and cloudflared | Orchestrates both containers |
mosquitto.conf | Mosquitto broker configuration | Defines listeners on ports 1883 and 9001 |
.env | Environment variables (contains CLOUDFLARE_TUNNEL_TOKEN) | Not version controlled (secret) |
.env.sample | Template for .env file | Shows required variables without actual values |
.gitignore | Git exclusions | Prevents committing .env and data/* |
.github/workflows/ci.yml | GitHub Actions CI pipeline | Tests 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
- No Direct Internet Exposure : The
mosquittobroker is never directly accessible from the internet - Outbound-Only Connection : The
cloudflaredcontainer initiates the tunnel connection to Cloudflare - Service Discovery : Docker's internal DNS resolves
mosquittoto themosquittocontainer docker-compose.yml6 - WebSocket Routing : Public traffic is routed to the WebSocket listener on port 9001 mosquitto.conf:4-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:
- Create a Cloudflare Tunnel and obtain the
CLOUDFLARE_TUNNEL_TOKENREADME.md:23-54 - Create a
.envfile with the token README.md51 - Configure a public hostname to route to
mosquitto:9001README.md62 - Run
docker compose upREADME.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
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:
- Cloudflare Tunnel Creation : Create and configure a tunnel in the Cloudflare Zero Trust dashboard
- Token Extraction : Obtain the
CLOUDFLARE_TUNNEL_TOKENfrom Cloudflare - Local Configuration : Create a
.envfile with the tunnel token - Container Deployment : Start the
mosquittoandcloudflaredservices 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:
- Access Zero Trust → Networks → Tunnels
- Click Create a tunnel
- Select Cloudflared as the tunnel type
- Provide a tunnel name (e.g.,
mqtt-tunnel) - On the connector installation screen, select Docker as the environment
- 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 Field | Value | Description |
|---|---|---|
| Public hostname | your-subdomain.yourdomain.com | The public URL clients will connect to |
| Service Type | HTTP | Protocol type for the tunnel |
| Service URL | mosquitto:9001 | Internal 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:
- Pulls the
eclipse-mosquitto:latestimage (if not cached) - Pulls the
cloudflare/cloudflared:latestimage (if not cached) - Starts the
mosquittoservice with mounted configuration from docker-compose.yml:7-8 - Starts the
cloudflaredservice 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 ID | IMAGE | COMMAND | STATUS | PORTS | NAMES |
|---|---|---|---|---|---|
<id> | eclipse-mosquitto:latest | /docker-entrypoint... | Up X seconds | 1883/tcp, 9001/tcp | mosquitto |
<id> | cloudflare/cloudflared:latest | tunnel --no-autoupdate... | Up X seconds | cloudflared |
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:
- External MQTT clients can connect to the public hostname configured in Step 2
- Traffic routes through Cloudflare's network to the
cloudflaredcontainer - The
cloudflaredcontainer proxies traffic tomosquitto:9001 - The
mosquittobroker 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
| Issue | Symptom | Resolution |
|---|---|---|
| Missing .env file | cloudflared fails to start with authentication error | Create .env file from .env.sample:1-3 template and add valid token |
| Invalid tunnel token | cloudflared logs show authentication failure | Verify token in Cloudflare dashboard and update .env file |
| Port conflicts | Container fails to start with port binding error | Check if ports 1883 or 9001 are already in use on the host |
| Tunnel not found | cloudflared reports tunnel does not exist | Ensure tunnel was created in Cloudflare dashboard and token matches |
| Public hostname not configured | Clients cannot connect | Configure public hostname in Cloudflare dashboard as described in Step 2 |
| Volume mount errors | mosquitto fails to load configuration | Verify 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
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 :
- docker-compose.yml:4-9 -
mosquittoservice definition - docker-compose.yml:11-17 -
cloudflaredservice definition
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 :
- All service definitions in docker-compose.yml
- Deployment command referenced in README.md:70-72
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 :
- Creating Cloudflare Tunnel (documented in README.md:27-54)
- Generating
CLOUDFLARE_TUNNEL_TOKENfor docker-compose.yml:13-17 - Configuring public hostname routing to
mosquitto:9001
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):
- Zero Trust dashboard access README.md:27-29
- Tunnel creation README.md:34-45
- 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 Area | Relevance | Related Components |
|---|---|---|
| MQTT Protocol | Understanding publish/subscribe messaging, topics, QoS levels | mosquitto service, MQTT clients |
| Docker Fundamentals | Container concepts, image management, volume mounts | All services in docker-compose.yml |
| Docker Compose | Service orchestration, networking, environment variables | docker-compose.yml structure |
| Networking Basics | Ports, protocols (TCP/WebSocket), DNS | Listener configuration, tunnel routing |
| Cloudflare Tunnels | Secure tunnel concepts, Zero Trust principles | cloudflared service operation |
| Linux Command Line | Basic shell commands, file permissions, process management | Deployment and troubleshooting |
Sources : README.md, docker-compose.yml
System Requirements
Hardware Requirements
The system has minimal hardware requirements suitable for most modern systems:
| Resource | Minimum | Recommended | Purpose |
|---|---|---|---|
| CPU | 1 core | 2+ cores | Container orchestration, MQTT message processing |
| RAM | 512 MB | 1 GB+ | mosquitto and cloudflared containers |
| Disk Space | 500 MB | 2 GB+ | Docker images, configuration files, optional data directory |
| Network | Internet connection | Stable broadband | Cloudflare 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 System | Support Status | Notes |
|---|---|---|
| Linux | Fully Supported | Native Docker support, recommended for production |
| macOS | Fully Supported | Requires Docker Desktop |
| Windows | Fully Supported | Requires Docker Desktop with WSL2 backend |
Sources : docker-compose.yml
Network Requirements
| Requirement | Description | Used By |
|---|---|---|
| Outbound HTTPS (443) | Required for cloudflared to establish tunnel | docker-compose.yml:11-17 |
| No Inbound Ports | System does not require any inbound firewall rules | Security model documented in Security Model |
| DNS Resolution | Required to resolve Cloudflare endpoints | cloudflared service |
| Internal Docker Network | Default bridge network for inter-container communication | Communication 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 Path | Purpose | Required For |
|---|---|---|
docker-compose.yml | Service orchestration configuration | All deployment operations |
mosquitto.conf | Mosquitto broker configuration | mosquitto service startup |
.env.sample | Template for environment variables | Creating .env file (see Environment Variables) |
.gitignore | Excludes secrets from version control | Protecting CLOUDFLARE_TUNNEL_TOKEN |
README.md | Setup documentation | Reference 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:
- Proceed to Cloudflare Tunnel Configuration to create a tunnel and obtain your
CLOUDFLARE_TUNNEL_TOKEN - Then configure environment variables as documented in Environment Variables
- Review Mosquitto Configuration to understand broker settings
- 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
This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
System Architecture
Relevant source files
Purpose and Scope
This document describes the technical architecture of the Docker MQTT Mosquitto Cloudflare Tunnel system, including its component structure, network topology, data flow patterns, and configuration management. This page focuses on the structural aspects of the system. For security-specific architecture details, see Security Model.
Sources: README.md, docker-compose.yml, mosquitto.conf
System Components Overview
The system consists of two primary Docker containers orchestrated by Docker Compose, with supporting configuration files that define their behavior.
| Component | Type | Image | Purpose |
|---|---|---|---|
mosquitto | Service Container | eclipse-mosquitto:latest | MQTT broker providing message routing and pub/sub functionality |
cloudflared | Service Container | cloudflare/cloudflared:latest | Cloudflare Tunnel client establishing secure outbound connection |
mosquitto.conf | Configuration File | N/A | Defines MQTT listener ports, protocols, and access policies |
docker-compose.yml | Orchestration | N/A | Defines service configurations, networking, and restart policies |
.env | Secrets File | N/A | Contains CLOUDFLARE_TUNNEL_TOKEN for tunnel authentication |
Sources: docker-compose.yml:1-18 mosquitto.conf:1-6
Container Architecture
The following diagram illustrates the two-container architecture and their configuration dependencies:
graph TB
subgraph DockerHost["Docker Host"]
subgraph ComposeStack["Docker Compose Stack"]
MosquittoContainer["mosquitto container"]
CloudflaredContainer["cloudflared container"]
end
subgraph ConfigFiles["Configuration Files"]
ComposeYML["docker-compose.yml"]
MosquittoConf["mosquitto.conf"]
EnvFile[".env"]
end
end
subgraph ExternalDeps["External Dependencies"]
MosquittoImage["eclipse-mosquitto:latest"]
CloudflaredImage["cloudflare/cloudflared:latest"]
CloudflareNetwork["Cloudflare Edge Network"]
end
ComposeYML -->|orchestrates| MosquittoContainer
ComposeYML -->|orchestrates| CloudflaredContainer
MosquittoImage -->|provides runtime| MosquittoContainer
CloudflaredImage -->|provides runtime| CloudflaredContainer
MosquittoConf -->|volume mount ./mosquitto.conf:/mosquitto/config/mosquitto.conf| MosquittoContainer
EnvFile -->|environment variable CLOUDFLARE_TUNNEL_TOKEN| CloudflaredContainer
CloudflaredContainer -->|tunnel --no-autoupdate run --token| CloudflareNetwork
MosquittoContainer -.->|internal proxy mosquitto:9001| CloudflaredContainer
Container Service Architecture
Sources: docker-compose.yml:1-18 mosquitto.conf:1-6
Service Configuration Details
The mosquitto service is defined in docker-compose.yml:4-9 with the following characteristics:
- Container Name:
mosquitto(used for internal DNS resolution) - Image:
eclipse-mosquitto:latest - Volume Mount:
./mosquitto.conf:/mosquitto/config/mosquitto.conf(read-only configuration) - Restart Policy:
unless-stopped(automatic recovery from failures)
The cloudflared service is defined in docker-compose.yml:11-17 with the following characteristics:
- Container Name:
cloudflared - Image:
cloudflare/cloudflared:latest - Command:
tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN} - Environment Variable:
CLOUDFLARE_TUNNEL_TOKEN(sourced from.envfile) - Restart Policy:
unless-stopped
Sources: docker-compose.yml:1-18
Network Topology
The system implements a multi-layer network architecture with both internal Docker networking and external Cloudflare tunnel connectivity.
graph TB
subgraph Internet["Public Internet"]
MQTTClients["MQTT Clients"]
end
subgraph CloudflareInfra["Cloudflare Infrastructure"]
CFEdge["Cloudflare Edge Network"]
TunnelService["Tunnel Service"]
end
subgraph DockerInternal["Docker Internal Network (default)"]
subgraph CloudflaredContainer["cloudflared container"]
TunnelClient["tunnel client process"]
end
subgraph MosquittoContainer["mosquitto container"]
Listener1883["listener 1883\nTCP MQTT"]
Listener9001["listener 9001\nWebSocket MQTT"]
end
end
MQTTClients -->|MQTT/WebSocket public hostname| CFEdge
CFEdge <-->|encrypted tunnel outbound from cloudflared| TunnelService
TunnelService <-->|tunnel connection| TunnelClient
TunnelClient -->|HTTP proxy to mosquitto:9001| Listener9001
Listener1883 -.->|not exposed externally local only| DockerInternal
Listener9001 -->|internal routing| TunnelClient
Network Layer Architecture
Sources: mosquitto.conf:1-6 docker-compose.yml:11-17 README.md:56-65
Port and Protocol Configuration
The mosquitto service exposes two listener ports configured in mosquitto.conf:1-6:
| Listener Port | Protocol | Configuration | Purpose | External Access |
|---|---|---|---|---|
| 1883 | TCP MQTT | listener 1883 | ||
allow_anonymous true | Standard MQTT protocol | Not exposed (container-internal only) | ||
| 9001 | WebSocket MQTT | listener 9001 | ||
protocol websockets | MQTT over WebSockets (inherits allow_anonymous true) | Proxied via Cloudflare Tunnel |
The cloudflared service does not expose any ports directly. It establishes an outbound connection to Cloudflare's network and proxies inbound traffic to mosquitto:9001 via Docker's internal DNS resolution.
Sources: mosquitto.conf:1-6 README.md:62
Data Flow Architecture
The following diagram illustrates the complete message flow from external clients through the Cloudflare Tunnel to the Mosquitto broker.
sequenceDiagram
participant Client as "MQTT Client"
participant CFEdge as "Cloudflare Edge"
participant Cloudflared as "cloudflared container"
participant Mosquitto as "mosquitto container"
participant MosqConf as "mosquitto.conf"
Note over Cloudflared: Startup initialization
Cloudflared->>Cloudflared: Read CLOUDFLARE_TUNNEL_TOKEN from environment
Cloudflared->>CFEdge: Execute: tunnel --no-autoupdate run --token
CFEdge-->>Cloudflared: Tunnel established (outbound connection)
Note over Mosquitto: Startup initialization
Mosquitto->>MosqConf: Load /mosquitto/config/mosquitto.conf
MosqConf-->>Mosquitto: Configure listener 1883 (TCP)
MosqConf-->>Mosquitto: Configure listener 9001 (WebSocket)
MosqConf-->>Mosquitto: Set allow_anonymous true
Note over Client,Mosquitto: MQTT Connection Phase
Client->>CFEdge: MQTT CONNECT via public hostname
CFEdge->>Cloudflared: Route through tunnel
Cloudflared->>Mosquitto: HTTP proxy to mosquitto:9001
Mosquitto->>Mosquitto: Validate listener 9001 (WebSocket)
Mosquitto->>Mosquitto: Check allow_anonymous (true)
Mosquitto-->>Cloudflared: MQTT CONNACK
Cloudflared-->>CFEdge: Tunnel response
CFEdge-->>Client: MQTT CONNACK
Note over Client,Mosquitto: MQTT Publish Phase
Client->>CFEdge: MQTT PUBLISH topic/message
CFEdge->>Cloudflared: Tunnel
Cloudflared->>Mosquitto: Proxy to :9001
Mosquitto->>Mosquitto: Route to topic subscribers
Mosquitto-->>Cloudflared: MQTT PUBACK
Cloudflared-->>CFEdge: Tunnel
CFEdge-->>Client: MQTT PUBACK
MQTT Message Flow Sequence
Sources: docker-compose.yml:11-17 mosquitto.conf:1-6 README.md:15-73
Internal Service Communication
Within the Docker Compose stack, services communicate using Docker's built-in DNS resolution. The cloudflared container references the mosquitto service by its container name (mosquitto) as configured in docker-compose.yml6
When configuring the Cloudflare Tunnel's public hostname, the service URL is set to mosquitto:9001 (as described in README.md:62), which resolves to:
- Hostname:
mosquitto→ Docker DNS resolves tomosquittocontainer's internal IP - Port:
9001→ The WebSocket listener defined in mosquitto.conf:4-5
No explicit Docker network configuration is required because both services use the default bridge network created by Docker Compose.
Sources: docker-compose.yml6 mosquitto.conf:4-5 README.md:62
Configuration File Relationships
The following diagram maps configuration files to their consuming containers and the specific configuration aspects they control:
graph LR
subgraph ConfigLayer["Configuration Layer"]
ComposeFile["docker-compose.yml"]
MosqConf["mosquitto.conf"]
EnvFile[".env"]
EnvSample[".env.sample"]
end
subgraph RuntimeLayer["Runtime Layer"]
MosqContainer["mosquitto container"]
CFContainer["cloudflared container"]
DockerEngine["Docker Engine"]
end
subgraph ConfigContent["Configuration Content"]
ServiceDef["Service definitions:\nimage, container_name,\nrestart policies"]
VolumeMount["Volume mount:\n./mosquitto.conf:/mosquitto/config/mosquitto.conf"]
EnvVar["Environment variable:\nCLOUDFLARE_TUNNEL_TOKEN"]
ListenerConf["Listeners:\n1883 (TCP)\n9001 (WebSocket)"]
AnonAccess["Access control:\nallow_anonymous true"]
TunnelToken["Tunnel token:\nCLOUDFLARE_TUNNEL_TOKEN=..."]
end
EnvSample -.->|template for| EnvFile
ComposeFile --> ServiceDef
ComposeFile --> VolumeMount
ComposeFile --> EnvVar
ServiceDef -->|defines| MosqContainer
ServiceDef -->|defines| CFContainer
VolumeMount -->|mounts into| MosqContainer
EnvVar -->|injects into| CFContainer
MosqConf --> ListenerConf
MosqConf --> AnonAccess
ListenerConf -->|configures| MosqContainer
AnonAccess -->|configures| MosqContainer
EnvFile --> TunnelToken
TunnelToken -->|provides to| CFContainer
DockerEngine -->|runs| MosqContainer
DockerEngine -->|runs| CFContainer
Configuration Dependency Graph
Sources: docker-compose.yml:1-18 mosquitto.conf:1-6
Configuration File Purposes
| File | Primary Consumer | Configuration Scope | Version Controlled |
|---|---|---|---|
docker-compose.yml | Docker Compose CLI | Service orchestration, container definitions, volumes, environment variable names | Yes |
mosquitto.conf | mosquitto container | MQTT listener configuration, protocol settings, access control | Yes |
.env | Docker Compose (injected into containers) | Secret values, particularly CLOUDFLARE_TUNNEL_TOKEN | No (excluded via .gitignore) |
.env.sample | Developers (documentation) | Template showing required environment variables | Yes |
The .env file is excluded from version control (see .gitignore) to prevent accidental exposure of the CLOUDFLARE_TUNNEL_TOKEN, which provides authentication credentials for the Cloudflare Tunnel.
Sources: docker-compose.yml:1-18 mosquitto.conf:1-6 README.md:51
Architectural Patterns
Reverse Proxy via Outbound Tunnel
The system implements a reverse proxy pattern where the cloudflared container acts as a secure tunnel endpoint that proxies external traffic to the internal mosquitto service. This pattern has several architectural implications:
- No Inbound Ports Required: The
cloudflaredcontainer initiates an outbound connection to Cloudflare's network (see docker-compose.yml14), eliminating the need for inbound firewall rules - Internal Service Discovery: The proxy uses Docker's internal DNS to resolve
mosquitto:9001to the appropriate container - Protocol Translation: External MQTT clients connect via standard protocols, while the tunnel uses HTTP/HTTPS for transport
Sources: docker-compose.yml:11-17 README.md:15-73
Stateless Container Design
Both containers follow a stateless design pattern :
mosquitto: No persistent data volume is configured in docker-compose.yml:4-9 meaning broker state (retained messages, subscriptions) is ephemeral and lost on container restartcloudflared: The tunnel client is stateless; all configuration is provided via theCLOUDFLARE_TUNNEL_TOKENenvironment variable
This design prioritizes simplicity and ease of deployment over data persistence. For persistent storage requirements, see Advanced Topics.
Sources: docker-compose.yml:1-18
Service Restart Policy
Both services use restart: unless-stopped (docker-compose.yml:9-15), which provides automatic recovery from failures while allowing manual stops to persist. This policy ensures:
- Containers automatically restart after crashes or Docker daemon restarts
- Manually stopped containers remain stopped (via
docker compose stop) - System reboots trigger container restarts
Sources: docker-compose.yml:9-15
Component Lifecycle
The following table describes the startup sequence and dependencies:
| Phase | Component | Action | Dependencies |
|---|---|---|---|
| 1 | Docker Compose | Parse docker-compose.yml | .env file must exist with CLOUDFLARE_TUNNEL_TOKEN |
| 2 | Docker Engine | Pull images if not cached | Internet connectivity to Docker Hub and Cloudflare registries |
| 3 | mosquitto container | Start, load /mosquitto/config/mosquitto.conf | mosquitto.conf must exist at mount path |
| 4 | mosquitto container | Initialize listeners on ports 1883 and 9001 | No port conflicts on host |
| 5 | cloudflared container | Start, execute tunnel command with token | Valid CLOUDFLARE_TUNNEL_TOKEN value |
| 6 | cloudflared container | Establish outbound tunnel to Cloudflare | Internet connectivity, valid tunnel configuration in Cloudflare dashboard |
| 7 | System Ready | Both containers running, tunnel connected | Cloudflare public hostname configured to route to mosquitto:9001 |
There is no explicit startup ordering defined in docker-compose.yml (no depends_on directive), meaning both containers start simultaneously. However, the cloudflared container will retry tunnel connections until successful, providing implicit resilience to timing issues.
Sources: docker-compose.yml:1-18 mosquitto.conf:1-6
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.
| Action | Location | Details |
|---|---|---|
| Log in | Cloudflare Dashboard | Use your Cloudflare account credentials |
| Navigate | Left sidebar | Select "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.
| Step | Action | Configuration |
|---|---|---|
| 1 | Navigate to Networks → Tunnels | Access tunnel management interface |
| 2 | Click "Create a tunnel" | Initialize tunnel creation workflow |
| 3 | Select tunnel type | Choose "Cloudflared" connector type |
| 4 | Name the tunnel | Enter descriptive name (e.g., my_tunnel_name) |
| 5 | Save tunnel | Finalize 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
| Action | File | Format |
|---|---|---|
| Copy token from dashboard | N/A | Long alphanumeric string from Docker command |
Create .env file | .env (root directory) | CLOUDFLARE_TUNNEL_TOKEN=<your_token> |
| Reference template | .env.sample:1 | Shows 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
| Parameter | Value | Description |
|---|---|---|
| Public hostname | User-defined subdomain and domain | External URL for MQTT clients to connect |
| Service type | HTTP | Protocol for tunnel transport (not MQTT protocol) |
| URL | mosquitto:9001 | Internal Docker service reference |
Important Implementation Details:
- The URL
mosquitto:9001uses Docker's internal DNS resolution mosquittoresolves to the container withcontainer_name: mosquittofrom docker-compose.yml6- Port
9001corresponds to the WebSocket listener configured in mosquitto.conf - The service type
HTTPrefers 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:
- Navigate to the newly created tunnel in the Zero Trust dashboard
- Click "Next" to proceed to hostname configuration
- Fill in the public hostname configuration form:
- Enter your desired subdomain and domain
- Select
HTTPas the service type - Enter
mosquitto:9001as the URL
- 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 Element | Value | Purpose |
|---|---|---|
image | cloudflare/cloudflared:latest | Official Cloudflare tunnel client |
container_name | cloudflared | DNS-resolvable name within Docker network |
command | tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN} | Runs tunnel with token from environment |
environment | - CLOUDFLARE_TUNNEL_TOKEN | Passes variable from .env to container |
restart | unless-stopped | Ensures 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
| Item | Location | Verification Method |
|---|---|---|
| Tunnel created in dashboard | Cloudflare Zero Trust → Networks → Tunnels | Tunnel appears in list with "Inactive" status |
| Public hostname configured | Tunnel details → Public Hostname tab | Hostname entry shows mosquitto:9001 as URL |
.env file exists | Repository root directory | File contains CLOUDFLARE_TUNNEL_TOKEN=<token> |
| Token format valid | .env:1 | Token is long alphanumeric string (typically 150+ characters) |
docker-compose.yml unchanged | Repository root directory | File matches repository version |
Expected State Before Deployment:
- Tunnel status in Cloudflare dashboard: "Inactive" (no connector running yet)
.envfile: 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
| Issue | Symptom | Solution |
|---|---|---|
| Invalid token format | cloudflared container exits immediately | Verify token copied completely from dashboard, check for extra spaces or newlines |
| Public hostname not resolving | DNS lookup fails for public hostname | Check DNS propagation, verify domain is active in Cloudflare |
| Wrong service URL | cloudflared starts but clients cannot connect | Ensure URL is mosquitto:9001, not localhost:9001 or IP address |
| Service type misconfiguration | Connection errors despite tunnel active | Service type must be HTTP, not TCP or other protocols |
| Token in version control | Security warning in git status | Verify .env is listed in .gitignore never commit .env file |
Sources: docker-compose.yml:11-17, README.md:47-67
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 Aspect | Implementation | File Reference |
|---|---|---|
| Inbound Ports | None required on host | N/A |
| Outbound Connection | HTTPS to Cloudflare edge | N/A |
| Container Network | Docker bridge (default) | docker-compose.yml |
| Service Resolution | DNS-based via container name | docker-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:
| Listener | Port | Protocol | Transport Security | Configuration |
|---|---|---|---|---|
| TCP | 1883 | MQTT | None (internal only) | mosquitto.conf1 |
| WebSocket | 9001 | MQTT over WS | Via Cloudflare Tunnel | mosquitto.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:
- Cloudflare Tunnel provides network-level access control - Only authorized clients can reach the broker through the configured public hostname
- All traffic reaching the broker is trusted - The broker does not differentiate between clients
- 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 Control | Status | Implication |
|---|---|---|
| Username/Password | Disabled | Any client can connect |
| ACL (Topic Permissions) | None | All clients can access all topics |
| Client ID Restrictions | None | Any client ID accepted |
| Connection Limits | None (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
| File | Version Controlled | Contains Secrets | Purpose |
|---|---|---|---|
.env.sample | Yes | No | Template/documentation |
.env | No (ignored) | Yes | Actual secrets |
docker-compose.yml | Yes | No | References ${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:
- Filesystem Permissions: The
.envfile should have restrictive permissions (e.g.,chmod 600 .env) - Container Environment: The token is visible via
docker inspect cloudflared - Process Environment: The token is visible to processes running as root on the Docker host
- Backup Security: Ensure backups of the host system protect the
.envfile
Sources: .env.sample:1, .gitignore:1
Trust Boundaries
Security Perimeter Mapping
The system defines three trust boundaries:
| Boundary | Security Mechanism | Threats Mitigated |
|---|---|---|
| Internet → Cloudflare | DDoS protection, WAF, geo-blocking | Network attacks, malicious traffic |
| Cloudflare → Docker Host | Tunnel token authentication, TLS encryption | Man-in-the-middle, unauthorized access |
| Host → Containers | Docker network isolation, no direct exposure | Container escape attacks |
| cloudflared → mosquitto | None (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:
| Aspect | Implementation | Limitation | Risk Level |
|---|---|---|---|
| MQTT Authentication | allow_anonymous true | No username/password | Medium |
| Topic Authorization | No ACL file | Any client can access any topic | Medium |
| Message Encryption | None at MQTT layer | Traffic visible within Docker network | Low |
| Client Identification | No client certificate | Cannot verify client identity | Medium |
| Rate Limiting | Cloudflare only | No MQTT-level rate limiting | Low |
Sources: mosquitto.conf:1-6
Appropriate Use Cases
This security model is appropriate for:
- Trusted Client Environments - All connecting clients are known and trusted
- Single-Tenant Deployments - One user/organization controls all clients
- Internal IoT Networks - Devices managed by the same entity
- Development/Testing - Non-production environments
Inappropriate Use Cases
This security model is not recommended for:
- Multi-Tenant Systems - Multiple independent users/organizations
- Public MQTT Brokers - Open access to untrusted clients
- Sensitive Data - Financial, health, or personally identifiable information
- 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:
-
Topic-Based Access Control (ACL)
- Restricts wildcard subscriptions across users
- Username-based topic isolation (first topic level = username)
- ACL file configuration
-
Encrypted Retained Messages
- Uses
gocryptfsfor message encryption at rest - Automatic saving after each retained message
- Encrypted
data/directory
- Uses
Comparison:
| Feature | Main Branch | Protected-No-Wildcard Branch |
|---|---|---|
| Authentication | Anonymous | Required (ACL-based) |
| Topic Wildcards | Unrestricted | Restricted per user |
| Retained Messages | Plaintext | Encrypted (gocryptfs) |
| Access Control | None | ACL 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
.envfile 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-wildcardbranch 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
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
| Property | Value |
|---|---|
| Host Path | ./mosquitto.conf |
| Container Path | /mosquitto/config/mosquitto.conf |
| Mount Type | Volume 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
| Setting | Value | Description |
|---|---|---|
| Port | 1883 | Standard MQTT port (IANA registered) |
| Protocol | TCP (implicit) | Default MQTT protocol when no protocol directive specified |
| IP Binding | All interfaces (implicit) | No explicit bind address, so listens on 0.0.0.0 |
| Authentication | Anonymous allowed | Governed 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
| Setting | Value | Description |
|---|---|---|
| Port | 9001 | Commonly used WebSocket MQTT port |
| Protocol | websockets | MQTT messages encapsulated in WebSocket frames |
| IP Binding | All interfaces (implicit) | Listens on 0.0.0.0 |
| Authentication | Anonymous allowed | Governed by global allow_anonymous true directive |
| Cloudflare Route | Yes | Proxied 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:
- Accept WebSocket handshake requests on port 9001
- Upgrade HTTP connections to WebSocket protocol
- Extract MQTT control packets from WebSocket frames
- 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
| Setting | Value | Security Impact |
|---|---|---|
| Directive | allow_anonymous | Controls whether unauthenticated connections are permitted |
| Value | true | All clients can connect without credentials |
| Scope | Global | Applies to all listeners (1883 and 9001) |
| ACL Enforcement | None | No access control list is configured in this file |
Security Implications
Important Security Considerations:
-
No Authentication: Any client that can reach the broker through the Cloudflare Tunnel can connect without providing credentials.
-
No Authorization: Connected clients have unrestricted access to:
- Publish to any topic
- Subscribe to any topic
- Use wildcard subscriptions (
#,+)
-
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)
-
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:
| Line | Directive | Value | Scope | Description |
|---|---|---|---|---|
| 1 | listener | 1883 | Listener-specific | Defines a network listener on port 1883 with default TCP protocol |
| 2 | allow_anonymous | true | Global | Permits connections without username/password authentication |
| 3 | (blank) | - | - | Separator for readability |
| 4 | listener | 9001 | Listener-specific | Defines a network listener on port 9001 |
| 5 | protocol | websockets | Listener-specific | Configures 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:
| Property | Value |
|---|---|
| Host Source | ./mosquitto.conf (relative to docker-compose.yml) |
| Container Destination | /mosquitto/config/mosquitto.conf |
| Mount Mode | Read-write (default), but container treats as read-only |
| Configuration Source | docker-compose.yml7-8 |
Configuration Loading Process
- Container Start: Docker Compose starts the mosquitto container per docker-compose.yml:4-9
- Volume Mount: Docker mounts
./mosquitto.confinto the container before process execution - Process Initialization: The mosquitto process starts and reads
/mosquitto/config/mosquitto.conf - Listener Creation: Mosquitto binds to ports 1883 and 9001 based on listener directives
- Ready State: Container enters healthy state, ready to accept connections
Runtime Behavior:
- Changes to
mosquitto.confon 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 downanddocker compose upto 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:
.envfile: Contains secrets, excluded via .gitignoredata/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
| Aspect | Benefit |
|---|---|
| Simplicity | Easy to understand for first-time users |
| Quick Setup | No complex authentication configuration required |
| Cloudflare Security | Network-level protection offloaded to Cloudflare Tunnel |
| Debugging | Fewer variables when troubleshooting connectivity |
Omitted Configurations
The following common Mosquitto directives are not present in mosquitto.conf:1-6:
password_file: No password-based authenticationacl_file: No topic-level access controlpersistence: No message persistence across restartslog_dest: Uses default logging to stdout (captured by Docker)max_connections: No connection limit imposedmax_queued_messages: Uses Mosquitto defaultsmessage_size_limit: Uses Mosquitto defaults
For implementations requiring these features, see Topic Access Control (ACL)) and Production Considerations.
Sources: mosquitto.conf:1-6
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 Name | Required | Purpose | Used By | Default Value |
|---|---|---|---|---|
CLOUDFLARE_TUNNEL_TOKEN | Yes | Authenticates the cloudflared tunnel client with Cloudflare's network | cloudflared service | None (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:
- The token was copied completely from the Cloudflare dashboard (no truncation)
- No extra spaces or newlines were added
- 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 :
- Verify
.envis in the same directory wheredocker compose upis executed - Restart services:
docker compose downthendocker compose up - Check that the variable name matches exactly (case-sensitive):
CLOUDFLARE_TUNNEL_TOKEN
Sources : .env.sample1 README.md51
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:
| Requirement | Verification Command | Expected Result |
|---|---|---|
| Docker Engine | docker --version | Docker version 20.x or higher |
| Docker Compose | docker compose version | Docker Compose version 2.x or higher |
.env file | test -f .env && echo "exists" | "exists" |
CLOUDFLARE_TUNNEL_TOKEN | grep CLOUDFLARE_TUNNEL_TOKEN .env | Non-empty token value |
docker-compose.yml | test -f docker-compose.yml && echo "exists" | "exists" |
mosquitto.conf | test -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 startedOpening websockets listen socket on port 9001- WebSocket listener started
cloudflared container:
Connection registered- Tunnel authenticated with CloudflareStarted 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
| Mode | Command | Use Case | Logs Visible | Terminal Blocked |
|---|---|---|---|---|
| Foreground | docker compose up | Development, debugging | Yes, real-time | Yes |
| Detached | docker compose up -d | Production, background services | No (use docker compose logs) | No |
| Force Recreate | docker compose up --force-recreate | Config changes not detected | Depends on flags | Depends on flags |
| Build & Start | docker compose up --build | Custom image changes | Depends on flags | Depends 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)
.envfile created withCLOUDFLARE_TUNNEL_TOKENdocker-compose.ymlpresent in working directorymosquitto.confpresent in working directory- Cloudflare Tunnel configured with public hostname
docker compose up -dexecuted without errorsdocker compose psshows both containers runningdocker compose logs mosquittoshows listener messagesdocker compose logs cloudflaredshows 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:
- Verify token in Cloudflare Zero Trust dashboard
- Check network connectivity:
ping cloudflare.com - Review
cloudflaredlogs:docker compose logs cloudflared - 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
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:
| Component | Container Name | Image | Primary Function |
|---|---|---|---|
| Mosquitto MQTT Broker | mosquitto | eclipse-mosquitto:latest | MQTT message broker providing pub/sub messaging |
| Cloudflared Tunnel Client | cloudflared | cloudflare/cloudflare:latest | Secure tunnel client connecting broker to Cloudflare network |
| Docker Compose | N/A | N/A | Service 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:
| Property | Value | Purpose |
|---|---|---|
image | eclipse-mosquitto:latest | Official Mosquitto Docker image |
container_name | mosquitto | Fixed container name for internal DNS resolution |
volumes | ./mosquitto.conf:/mosquitto/config/mosquitto.conf | Mounts configuration file into container |
restart | unless-stopped | Automatic 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 :
| Aspect | Implication |
|---|---|
| Authentication | None - all clients are accepted |
| Authorization | No topic-level access control in main branch |
| Trust model | Broker trusts all traffic from cloudflared |
| Network security | Security 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:
| Property | Value | Purpose |
|---|---|---|
image | cloudflare/cloudflared:latest | Official Cloudflare tunnel client image |
container_name | cloudflared | Fixed container name |
command | tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN} | Starts tunnel with authentication token |
restart | unless-stopped | Automatic restart policy |
environment | CLOUDFLARE_TUNNEL_TOKEN | Passes 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}
| Argument | Purpose |
|---|---|
tunnel | Primary cloudflared operation mode |
--no-autoupdate | Disables automatic updates (container image updates handled via Docker) |
run | Runs the tunnel in persistent mode |
--token | Authenticates 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:
mosquittoresolves via Docker's internal DNS to the container withcontainer_name: mosquitto- Port
9001corresponds 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 :
| Aspect | Behavior |
|---|---|
| Network type | Bridge network (default) |
| Network name | {project_name}_default (typically docker-mqtt-mosquitto-cloudflare-tunnel_default) |
| DNS resolution | Container names resolve to container IPs (mosquitto → container IP) |
| Inter-service communication | All services can reach each other using container names |
| External connectivity | Services 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 Type | Purpose |
|---|---|---|---|
./mosquitto.conf | /mosquitto/config/mosquitto.conf | Bind mount | Provides 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:
- Read
CLOUDFLARE_TUNNEL_TOKENfrom the.envfile in the same directory - Pass it as an environment variable to the
cloudflaredcontainer - Make it available for substitution in the
commanddirective 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 :
| Scenario | Behavior |
|---|---|
| Container exits with error | Automatically restarts |
| Container exits cleanly (exit 0) | Automatically restarts |
| Docker daemon restarts | Containers restart automatically |
Manual docker stop | Container remains stopped after Docker daemon restart |
| System reboot | Containers 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 :
| Command | Effect |
|---|---|
docker compose up | Creates and starts both services in foreground |
docker compose up -d | Creates and starts both services in background (detached) |
docker compose down | Stops and removes containers, networks |
docker compose restart | Restarts all services |
docker compose restart mosquitto | Restarts only Mosquitto service |
docker compose logs | Shows logs from all services |
docker compose logs -f cloudflared | Follows logs from cloudflared service |
For detailed deployment procedures, see Deployment.
Sources : docker-compose.yml, README.md:68-72
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
| Listener | Port | Protocol | Anonymous Access | Primary Use Case |
|---|---|---|---|---|
| mosquitto.conf1 | 1883 | Standard MQTT/TCP | Enabled globally mosquitto.conf2 | Direct MQTT client connections |
| mosquitto.conf:4-5 | 9001 | MQTT over WebSockets | Enabled globally mosquitto.conf2 | Browser-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 1883mosquitto.conf1: Binds the Mosquitto broker to TCP port 1883allow_anonymous truemosquitto.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 9001mosquitto.conf4: Binds the Mosquitto broker to TCP port 9001protocol websocketsmosquitto.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:
- External client connects to Cloudflare public hostname
- Cloudflare Edge routes to
cloudflaredcontainer via tunnel cloudflaredproxies HTTP traffic tomosquitto:9001- 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 :
- Cloudflare tunnel target:
mosquitto:9001README.md62 - WebSocket listener: mosquitto.conf:4-5
- TCP listener: mosquitto.conf1
- Container name resolution:
mosquittofromdocker-compose.yml
Sources : mosquitto.conf, README.md
Configuration Reference
Listener Directive Syntax
The listener directive defines a network interface and port for the broker to bind to:
listener <port> [bind_address]
Current Configuration :
- mosquitto.conf1:
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 Aspect | Listener 1883 | Listener 9001 |
|---|---|---|
| Port | 1883 mosquitto.conf1 | 9001 mosquitto.conf4 |
| Protocol | MQTT/TCP (implicit default) | WebSockets mosquitto.conf5 |
| Anonymous Access | Enabled mosquitto.conf2 | Enabled mosquitto.conf2 |
| Cloudflare Routing | Not configured | Configured README.md62 |
| External Accessibility | Docker internal only | Via Cloudflare tunnel |
Sources : mosquitto.conf, README.md
Use Case Selection
When to Use Listener 1883 (TCP)
- Native MQTT client libraries : Python Paho, Mosquitto C library, MQTT.js with TCP
- High-performance IoT devices : Minimal protocol overhead
- Internal Docker services : Direct container-to-container communication
- Legacy MQTT applications : Standard MQTT port compatibility
When to Use Listener 9001 (WebSockets)
- Browser-based clients : JavaScript MQTT clients in web applications
- Cloudflare tunnel access : External clients connecting via the public hostname
- Firewall-restricted networks : HTTP/WebSocket traffic often allowed through corporate firewalls
- CDN/proxy compatibility : Works with HTTP-based reverse proxies
Current System Configuration
The current deployment configures the Cloudflare tunnel to route exclusively to listener 9001. This means:
- External clients must use MQTT over WebSockets
- The public hostname README.md60 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
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 Type | Anonymous Client Capabilities |
|---|---|
| SUBSCRIBE | Can subscribe to any topic, including wildcards (#, +) |
| PUBLISH | Can publish to any topic |
| RETAIN | Can set retained messages on any topic |
| QoS Levels | Full access to QoS 0, 1, and 2 |
| Topic Restrictions | None - 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 Layer | Protection Provided | Configuration |
|---|---|---|
| Cloudflare Edge | DDoS protection, traffic filtering | Cloudflare Dashboard |
| Cloudflare Tunnel | Encrypted transport, no inbound ports | CLOUDFLARE_TUNNEL_TOKEN |
| Docker Network | Internal isolation between containers | docker-compose.yml |
| Mosquitto Authentication | None - anonymous access enabled | allow_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:
| Scenario | Rationale |
|---|---|
| Single-User Systems | One person controls all MQTT clients and traffic |
| Private/Home Networks | Network-level security already restricts access |
| Trusted Development Environments | Quick setup for testing without credential management |
| Internal IoT Networks | All devices within a trusted network boundary |
| Proof-of-Concept Deployments | Rapid 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
| Line | Directive | Effect |
|---|---|---|
| 1 | listener 1883 | Defines standard MQTT listener |
| 2 | allow_anonymous true | Enables anonymous access globally |
| 3 | (blank) | |
| 4 | listener 9001 | Defines WebSocket listener |
| 5 | protocol websockets | Specifies 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
| Aspect | Main Branch (Anonymous) | Protected-No-Wildcard Branch |
|---|---|---|
| Authentication | None required | Username/password required |
| Topic Access | Unrestricted | ACL file with per-user restrictions |
| Wildcard Subscriptions | Fully allowed | Restricted by ACL rules |
| Configuration Complexity | Minimal (6 lines) | Additional ACL file management |
| Use Case | Trusted environments | Multi-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:
- Start the services:
docker compose up - Connect any MQTT client to the public hostname configured in Cloudflare
- Attempt to connect without providing credentials
- 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:
- Creating a password file with
mosquitto_passwdcommand - Adding
password_file /path/to/password_filedirective to mosquitto.conf - Removing or changing
allow_anonymous truetoallow_anonymous false - Optionally adding an ACL file for topic-level restrictions
- 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
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:
| Property | Value | Purpose |
|---|---|---|
image | eclipse-mosquitto:latest | Container image specification |
container_name | mosquitto | Fixed container name for DNS resolution |
volumes | ./mosquitto.conf:/mosquitto/config/mosquitto.conf | Configuration file mount |
restart | unless-stopped | Automatic 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:
| Event | Container Behavior |
|---|---|
| Container crash | Automatically restarts |
| Docker daemon restart | Automatically restarts |
Manual docker stop | Remains stopped |
Manual docker compose down | Removed (not restarted) |
| System reboot | Automatically 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:
- Cloudflared container resolves
mosquittohostname via Docker DNS - Cloudflared establishes HTTP connection to mosquitto container port 9001
- Cloudflared proxies WebSocket upgrade requests to mosquitto
- 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:latestimage 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 Aspect | Location | Value |
|---|---|---|
| Service name | docker-compose.yml:4 | mosquitto |
| Container name | docker-compose.yml:6 | mosquitto |
| Image | docker-compose.yml:5 | eclipse-mosquitto:latest |
| Configuration file (host) | docker-compose.yml:8 | ./mosquitto.conf |
| Configuration file (container) | docker-compose.yml:8 | /mosquitto/config/mosquitto.conf |
| Restart policy | docker-compose.yml:9 | unless-stopped |
| Standard MQTT listener | mosquitto.conf:1 | Port 1883 |
| WebSocket listener | mosquitto.conf:4 | Port 9001 |
| Anonymous access | mosquitto.conf:2 | true |
| Data persistence | N/A | None (in-memory only) |
Sources: docker-compose.yml:4-9 mosquitto.conf:1-6
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 Aspect | Value | Purpose |
|---|---|---|
| Service Name | cloudflared | Identifier used by Docker Compose |
| Image | cloudflare/cloudflared:latest | Official Cloudflare Tunnel client image |
| Container Name | cloudflared | Hostname accessible within Docker network |
| Command | tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN} | Starts tunnel with authentication token |
| Restart Policy | unless-stopped | Automatically restarts on failure or reboot |
| Environment Variables | CLOUDFLARE_TUNNEL_TOKEN | Tunnel 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:
- Uniquely identifies the tunnel - Each tunnel has a unique token that maps to a specific tunnel configuration in Cloudflare's system
- Contains embedded authentication - The token includes all necessary authentication information; no username/password is required
- 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
.envfile is excluded via.gitignore - Stored only in local environment - Each deployment maintains its own
.envfile - 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:
| Characteristic | Behavior |
|---|---|
| Direction | Outbound-only from cloudflared to Cloudflare edge |
| Protocol | Encrypted (TLS) over TCP |
| Persistence | Long-lived connection, automatically reconnects if dropped |
| Firewall Requirements | None - no inbound ports need to be opened |
| Restart Behavior | Container 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:
- Public hostname - Configured in Cloudflare dashboard (e.g.,
mqtt.example.com) - Service type - Set to HTTP in the dashboard
- Target URL - Set to
mosquitto:9001README.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:
- DNS-based service discovery - The hostname
mosquittoautomatically resolves to the IP address of the mosquitto container - Direct container-to-container communication - Traffic between cloudflared and mosquitto stays within the Docker network
- 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:
| Scenario | Behavior |
|---|---|
| Container crashes | Automatically restarts |
| Docker daemon restarts | Container starts automatically |
| System reboot | Container starts automatically |
| Manual stop | Container 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:
- Routing target - The tunnel routes traffic to
mosquitto:9001 - Service availability - If mosquitto is not running, routed traffic will fail
- Docker DNS resolution - The hostname
mosquittomust resolve for proxying to work
The lack of an explicit dependency is acceptable because:
- Both services have
restart: unless-stoppedpolicies, 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
| State | Indication | Meaning |
|---|---|---|
| Running | Container status shows "Up" | Tunnel is active and routing traffic |
| Restarting | Container repeatedly starts and stops | Authentication failure or network issue |
| Exited | Container stopped with exit code | Configuration 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:latestrunning withcontainer_name: cloudflared - Authentication: Token-based via
CLOUDFLARE_TUNNEL_TOKENenvironment variable - Command:
tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN} - Restart Policy:
unless-stoppedfor 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
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.
| Property | Value | Purpose |
|---|---|---|
image | eclipse-mosquitto:latest | Specifies the official Eclipse Mosquitto Docker image from Docker Hub |
container_name | mosquitto | Assigns a fixed container name for predictable hostname resolution within the Docker network |
volumes | ./mosquitto.conf:/mosquitto/config/mosquitto.conf | Mounts the local configuration file into the container's expected config path |
restart | unless-stopped | Configures 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.
| Property | Value | Purpose |
|---|---|---|
image | cloudflare/cloudflared:latest | Specifies the official Cloudflare Tunnel client Docker image |
container_name | cloudflared | Assigns a fixed container name for network identification |
command | tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN} | Overrides the default container command to execute the tunnel client with authentication |
environment | CLOUDFLARE_TUNNEL_TOKEN | Declares the environment variable that Docker Compose should inject from the .env file |
restart | unless-stopped | Configures 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:
- External → cloudflared : Inbound traffic arrives via the Cloudflare Tunnel (outbound-initiated connection)
- cloudflared → mosquitto : Internal Docker network communication on port 9001
- 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 containingdocker-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:
- Edit the file on the host filesystem
- 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
| Scenario | Container Behavior | Rationale |
|---|---|---|
| Container crashes | Automatically restarts | Recovers from application failures |
| Docker daemon restarts | Automatically restarts | Maintains service availability after host reboot |
Manual docker stop | Does not restart | Respects operator intent to stop service |
Manual docker compose down | Does not restart | Stops all services as expected |
| Host system reboot | Automatically 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
mosquittoservice can start independently - The
cloudflaredservice will retry connections tomosquitto:9001if 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:
- Docker Engine (to run containers)
- Active internet connection (for cloudflared to reach Cloudflare's network)
- Valid Cloudflare Tunnel token (from Cloudflare Zero Trust dashboard)
Configuration Dependencies :
mosquittoservice depends on the existence of./mosquitto.confdocker-compose.yml8cloudflaredservice depends onCLOUDFLARE_TUNNEL_TOKENbeing set in.envdocker-compose.yml:16-17
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
initflag (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
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:
- Setting up your local development environment, see Local Development Setup
- Understanding the automated testing system, see CI/CD Pipeline
- Managing secrets and version control, see Version Control Practices
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:
| File | Version Controlled | Purpose | Developer Action |
|---|---|---|---|
docker-compose.yml | Yes | Service orchestration | Modify service definitions |
mosquitto.conf | Yes | MQTT broker configuration | Adjust broker settings |
.env.sample | Yes | Environment variable template | Reference for configuration |
.env | No | Actual secrets and tokens | Create locally from template |
.gitignore | Yes | Defines excluded files | Update when adding new excludes |
.github/workflows/ci.yml | Yes | CI/CD pipeline definition | Modify test procedures |
data/* | No | Runtime data and state | Created 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_TOKENfrom.envfile - 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 theCLOUDFLARE_TUNNEL_TOKENsecretdata/*- 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 Aspect | Implementation | Location |
|---|---|---|
| Trigger | Push or PR to main branch | .github/workflows/ci.yml:3-9 |
| Environment | ubuntu-latest runner | .github/workflows/ci.yml13 |
| Service Under Test | mosquitto only | .github/workflows/ci.yml25 |
| Health Check | Container status polling | .github/workflows/ci.yml:28-39 |
| Timeout | 100 seconds (10 × 10s) | .github/workflows/ci.yml29 |
| Cleanup | Always 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:
| Command | Purpose | Effect |
|---|---|---|
docker-compose up -d | Start all services in background | Starts both mosquitto and cloudflared |
docker-compose up -d mosquitto | Start only mosquitto | Starts mosquitto without cloudflared |
docker-compose ps | Check service status | Shows running containers and their state |
docker-compose logs -f mosquitto | View mosquitto logs | Streams log output in real-time |
docker-compose down | Stop and remove services | Stops containers and removes them |
docker-compose restart mosquitto | Restart mosquitto | Stops 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:
- Setup Local Environment: Follow the detailed instructions in Local Development Setup to configure your workstation
- Understand CI Pipeline: Review CI/CD Pipeline for details on how automated tests validate changes
- 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
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 Item | Purpose | Reason for Exclusion |
|---|---|---|
.env | Contains CLOUDFLARE_TUNNEL_TOKEN | Sensitive authentication credential |
data/* | Runtime data generated by Mosquitto | Instance-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:
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
| File | Purpose | Consumer | Mount/Injection Method |
|---|---|---|---|
docker-compose.yml | Service orchestration | Docker Compose | N/A |
mosquitto.conf | Broker settings | mosquitto container | Volume mount at line 8 |
.env | Secrets and tokens | cloudflared container | Environment 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:
- Reads
docker-compose.ymlfor service definitions - Loads environment variables from
.env - Starts the
mosquittocontainer with the volume-mountedmosquitto.conf - Starts the
cloudflaredcontainer with theCLOUDFLARE_TUNNEL_TOKENenvironment variable
Sources : docker-compose.yml:1-18
Service Definitions
The docker-compose.yml defines two services:
mosquitto Service
| Property | Value | Purpose |
|---|---|---|
image | eclipse-mosquitto:latest | Official Mosquitto MQTT broker image |
container_name | mosquitto | Fixed container name for service discovery |
volumes | ./mosquitto.conf:/mosquitto/config/mosquitto.conf | Mounts local configuration into container |
restart | unless-stopped | Automatic restart policy |
cloudflared Service
| Property | Value | Purpose |
|---|---|---|
image | cloudflare/cloudflared:latest | Official Cloudflare Tunnel client image |
container_name | cloudflared | Fixed container name for service discovery |
command | tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN} | Tunnel client startup with token substitution |
restart | unless-stopped | Automatic restart policy |
environment | CLOUDFLARE_TUNNEL_TOKEN | Passes 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
.envfile is manually created and maintained by each developer - The
data/directory contains local runtime state - Services restart automatically via
unless-stoppedpolicy 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:
| Pattern | Matches | Reason |
|---|---|---|
.env | Exact file match | Contains CLOUDFLARE_TUNNEL_TOKEN secret |
data/* | All files in data/ directory | Instance-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:
- Edit
mosquitto.conflocally - Restart the
mosquittoservice:
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:
- Edit
.envfile - Restart the
cloudflaredservice:
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:
- Repository cloning from GitHub
- Environment file creation by copying
.env.sampleto.env - Token acquisition from Cloudflare Dashboard
- 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
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
mosquittoservice from docker-compose.yml:4-9 - Trigger Events : Push and pull request events to the
mainbranch - 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 Type | Branches | Configuration |
|---|---|---|
push | main | .github/workflows/ci.yml:4-6 |
pull_request | main | .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:
| Parameter | Value | Configuration |
|---|---|---|
| Maximum Attempts | 10 | .github/workflows/ci.yml29 |
| Interval Between Attempts | 10 seconds | .github/workflows/ci.yml35 |
| Total Timeout | 100 seconds | Calculated (10 × 10) |
| Success Condition | Container 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:
- Queries the container's state using
docker inspect - Extracts the
Statusfield from the container state - Checks if the status contains the string
running - 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:
| Aspect | Validation Method | Coverage |
|---|---|---|
| Container Image Availability | docker-compose up attempts to pull eclipse-mosquitto:latest | Verifies Docker Hub connectivity and image existence |
| Container Creation | Docker Compose service instantiation | Validates service definition in docker-compose.yml:4-9 |
| Configuration Validity | Container starts without errors | Ensures mosquitto.conf:1-7 is syntactically valid |
| Volume Mount | Implicit test during container startup | Verifies ./mosquitto.conf mount in docker-compose.yml8 |
| Process Startup | Container reaches running state | Confirms 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 Test | Reason |
|---|---|
| MQTT Protocol Connectivity | No client connection attempts to ports 1883 or 9001 |
| Message Publishing/Subscribing | Requires MQTT client integration |
| WebSocket Protocol | No WebSocket connection tests |
| Network Reachability | Containers not accessible from outside GitHub Actions network |
| Performance/Load Testing | Not within scope of basic health check |
| Authentication/Authorization | Current 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:
- Started successfully using the configuration from mosquitto.conf:1-7
- Loaded the volume mount from docker-compose.yml8
- Initialized the Mosquitto broker process
- Reached a stable
runningstate
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 Type | Manifestation | Common Cause |
|---|---|---|
| Image Pull Failure | Error pulling eclipse-mosquitto:latest | Docker Hub connectivity or rate limiting |
| Syntax Error | YAML parsing error | Invalid docker-compose.yml:1-18 syntax |
| Volume Mount Error | Cannot mount ./mosquitto.conf | File not found in repository |
| Port Conflict | Address already in use | Unlikely 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
- Navigate to the Actions tab in the GitHub repository
- Select the failed workflow run
- Click on the
test-mosquittojob - 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
| Limitation | Impact | Mitigation |
|---|---|---|
| No Cloudflared Testing | Cannot validate tunnel connectivity | Manual testing required in deployment environment |
| No Protocol Testing | Cannot verify MQTT/WebSocket functionality | Consider integration tests in separate workflow |
| No Persistence Testing | Does not verify data retention | Mosquitto stores no data in default configuration |
| No Security Testing | Anonymous access not validated | Current 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
mosquittoservice (excludescloudflared) - 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
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:
| Category | Files | Purpose |
|---|---|---|
| Orchestration | docker-compose.yml | Service definitions and container orchestration |
| Configuration Templates | mosquitto.conf, .env.sample | Non-sensitive configuration and secret templates |
| Documentation | README.md, LICENSE | Project documentation and licensing |
| CI/CD | .github/workflows/ci.yml | Automated testing configuration |
| Version Control Config | .gitignore | Exclusion 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 Pattern | Purpose | Rationale |
|---|---|---|
.env | Sensitive credentials | Contains CLOUDFLARE_TUNNEL_TOKEN and other secrets |
data/* | Runtime artifacts | Generated 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:
-
Copy the template:
-
Obtain actual credentials from the Cloudflare Zero Trust dashboard (see Cloudflare Tunnel Configuration)
-
Replace placeholders in
.envwith real values:CLOUDFLARE_TUNNEL_TOKEN=eyJhIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoifQ... -
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:
-
Evaluate for secrets: If the file will contain credentials, API keys, or tokens:
- Create a
.sampleor.exampleversion with placeholders - Commit the template file
- Add the actual filename to .gitignore:1-3
- Update documentation to explain required variables
- Create a
-
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
-
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:
- Do not simply delete the file and commit again (history still contains it)
- Immediately rotate the compromised credentials
- Remove from history using
git filter-branchorBFG Repo-Cleaner - Force push to rewrite remote history (only if repository is not shared)
- 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:
| Step | Command | Result |
|---|---|---|
| 1. Clone repository | git clone <repo-url> | Repository with templates only |
| 2. Copy env template | cp .env.sample .env | Local .env file created |
| 3. Configure secrets | Edit .env with real values | System ready to deploy |
| 4. Verify exclusion | git status | .env should not appear |
Sources: .env.sample:1-3 .gitignore1
Pull Requests and Code Review
When reviewing pull requests:
- Verify no
.envfiles 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
.gitignoreexclusion rules - Template files like
.env.sample
Developers should never commit:
- The actual
.envfile (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:
- The CI environment does not require
CLOUDFLARE_TUNNEL_TOKEN(tests only verify Mosquitto startup) - The cloudflared service is not started during CI tests
- 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:
- Security: Secrets remain local and never enter Git history (enforced by .gitignore1)
- Clarity: Templates provide clear documentation of required configuration (via .env.sample1)
- 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
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:
| Feature | main Branch | protected-no-wildcard Branch |
|---|---|---|
| Anonymous Access | Enabled (mosquitto.conf2) | User authentication required |
| Topic Access Control | None | ACL file with user-based restrictions |
| Wildcard Topic Queries | Unrestricted | Restricted to user's own topics |
| Retained Message Storage | Plaintext | Encrypted with gocryptfs |
| Message Persistence | Manual save | Auto-save after every message |
| Use Case | Development, trusted environments | Production, 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:
- ACL File : Located at mosquitto/aclfile in the protected branch, this file defines user-specific topic access patterns
- 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:
- Each retained message write triggers a disk synchronization
- Retained messages survive unexpected container restarts
- 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:
| Scenario | Recommended Branch | Rationale |
|---|---|---|
| Local development | main | Simpler configuration, no authentication overhead |
| Single trusted user | main | Anonymous access is sufficient for trusted environments |
| IoT prototyping | main | Rapid iteration without user management |
| Multi-tenant deployment | protected-no-wildcard | Topic isolation prevents cross-user access |
| Production with sensitive data | protected-no-wildcard | Encryption protects retained messages at rest |
| High-availability requirements | protected-no-wildcard | Auto-save ensures message persistence |
| Public-facing broker | protected-no-wildcard | Authentication and ACLs prevent abuse |
Migration Path : Moving from main to protected-no-wildcard requires:
- Adding user authentication credentials to Mosquitto configuration
- Creating an ACL file with appropriate topic rules
- Configuring and initializing the
gocryptfsencrypted filesystem - Updating client applications to provide authentication credentials
- Restructuring topic hierarchies to follow the username-first pattern
Sources : README.md:5-11
Accessing Advanced Features
To explore or deploy the advanced features:
-
View the diff : Compare the branches to understand specific changes
-
Checkout the branch :
-
Review documentation : Each subsection below provides detailed configuration instructions:
- Topic Access Control (ACL)) - User authentication and ACL file configuration
- Encrypted Retained Messages - Gocryptfs setup and encryption key management
- Production Considerations - Scaling, monitoring, and hardening recommendations
Sources : README.md11
Configuration File Locations
The following files are specific to the protected-no-wildcard branch and do not exist in main:
| File Path | Purpose | Branch |
|---|---|---|
mosquitto/aclfile | Defines user-based topic access rules | protected-no-wildcard only |
mosquitto/passwordfile | Stores hashed user credentials | protected-no-wildcard only |
docker-compose.yml (modified) | Includes gocryptfs service definition | protected-no-wildcard only |
data-encrypted/ | Encrypted filesystem storage directory | protected-no-wildcard only |
Files that exist in both branches but have different configurations:
| File Path | Main Branch | Protected Branch |
|---|---|---|
| mosquitto.conf2 | allow_anonymous true | allow_anonymous false |
| mosquitto.conf | No acl_file directive | Includes acl_file /mosquitto/config/aclfile |
| mosquitto.conf | No password_file directive | Includes 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:
- Topic Access Control (ACL)) for multi-user topic isolation
- Encrypted Retained Messages for filesystem encryption setup
- Production Considerations for deployment best practices
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
| Feature | Main Branch | Protected-No-Wildcard Branch |
|---|---|---|
| Anonymous Access | Enabled (allow_anonymous true) | Disabled |
| ACL File | Not present | mosquitto/aclfile |
| User Authentication | Not required | Required |
| Topic Restrictions | None | Username-prefixed topics |
| Wildcard Searches | Unrestricted | Restricted 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 Directive | Description |
|---|---|
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 |
# wildcard | Matches all remaining topic levels |
+ wildcard | Matches 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
| Pattern | Description | Allowed |
|---|---|---|
alice/devices/+ | Single-level wildcard within user namespace | ✓ Yes |
alice/sensors/# | Multi-level wildcard within user namespace | ✓ Yes |
alice/+/temperature | Single-level wildcard in middle of path | ✓ Yes |
Blocked Wildcards
| Pattern | Description | Blocked Reason |
|---|---|---|
# | Root-level multi-level wildcard | Would access all users' topics |
+/sensors/# | Wildcard at username level | Would access multiple users' topics |
alice/../bob/sensors/# | Topic traversal attempt | Not 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
- mosquitto/aclfile : Create this file with appropriate user and topic rules
- Password file (if using password authentication): Use
mosquitto_passwdutility 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:
| Method | Configuration | Use Case |
|---|---|---|
| Password File | password_file /mosquitto/config/passwords | Simple username/password authentication |
| Plugin Auth | auth_plugin /path/to/plugin.so | Database-backed authentication |
| PSK | psk_file /mosquitto/config/psk_file | Pre-shared key authentication |
| Certificate | require_certificate true | TLS 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
- Topic Isolation : Users cannot access topics outside their namespace
- Wildcard Restriction : Prevents broad topic enumeration by unauthorized users
- Granular Control : Different read/write permissions per topic pattern
- Audit Trail : Clear definition of who can access what
Limitations
- Naive Implementation : The "naive" approach mentioned in the README indicates this is a basic pattern-matching implementation
- Topic Format Dependency : Security relies on clients following the
username/topic prefix convention - No Dynamic ACL : Changes to the ACL file require container restart
- 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/aclfilewith user permissions - Update
mosquitto.confto disable anonymous access - Update
mosquitto.confto reference the ACL file - Create authentication mechanism (password file, plugin, etc.)
- Update
docker-compose.ymlto 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
Related Advanced Features
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
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:
- Encryption of retained messages using gocryptfs
- 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:
- Stores the message associated with the topic
- Delivers the message to any future subscribers to that topic
- 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
| Feature | Standard Mosquitto | Auto-Save Implementation |
|---|---|---|
| Persistence Timing | Periodic or on shutdown | After every retained message |
| Data Loss Window | Potential loss since last save | Minimal (only in-flight messages) |
| Disk I/O | Batched operations | Per-message writes |
| Recovery Guarantee | Last saved state | Most 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:
- Creating an encrypted filesystem on the host
- Mounting the encrypted filesystem using gocryptfs within the container
- Configuring Mosquitto to use the mounted directory for persistence
- 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
| Threat | Protection Provided | Limitations |
|---|---|---|
| Physical disk theft | Encrypted data at rest | Key must be protected separately |
| Unauthorized filesystem access | Encrypted files unreadable | Requires secure key storage |
| Memory dumps | Data encrypted on disk | Data decrypted in memory |
| Network interception | N/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
| Scenario | Encrypted Messages Recommended | Rationale |
|---|---|---|
| Public cloud deployment | Yes | Protect against cloud provider access |
| Home IoT network | Optional | Lower risk environment |
| Industrial sensors | Yes | Proprietary data protection |
| Development/testing | No | Performance 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:
- Back up existing retained messages from the
data/directory - Initialize a gocryptfs encrypted filesystem
- Deploy the
protected-no-wildcardbranch configuration - 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
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:
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 Setting | Purpose | Implementation |
|---|---|---|
allow_anonymous false | Disable anonymous connections | Replace line 2 in mosquitto.conf |
password_file /mosquitto/config/passwordfile | Enable username/password authentication | Add volume mount in docker-compose.yml |
acl_file /mosquitto/config/aclfile | Implement topic-level access control | Mount 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 Mount | Purpose | Considerations |
|---|---|---|
./data:/mosquitto/data | Persist retained messages and subscriptions | Ensure host directory has appropriate permissions |
./log:/mosquitto/log | Persist broker logs | Configure 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:
- Installing gocryptfs on the Docker host
- Creating an encrypted filesystem mounted at the data directory
- 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 Target | Frequency | Retention | Implementation |
|---|---|---|---|
| Mosquitto data directory | Daily | 30 days | Filesystem backup tools (rsync, restic, etc.) |
| mosquitto.conf | On change | Version control | Git commit and push |
| Password and ACL files | On change | Version control with encryption | git-crypt or external secrets management |
| Cloudflare tunnel configuration | N/A | Cloudflare dashboard maintains configuration | Document tunnel setup procedures |
Disaster Recovery Testing
Regularly test restoration procedures:
- Stop the
mosquittoservice - Restore backed-up data directory
- Verify container startup with
docker compose logs mosquitto - 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:
| Driver | Use Case | Configuration |
|---|---|---|
syslog | Central syslog server | Requires syslog-address option |
fluentd | Log aggregation pipeline | Requires Fluentd server |
awslogs | AWS CloudWatch integration | Requires 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:
| Topic | Metric | Threshold Alert |
|---|---|---|
$SYS/broker/clients/connected | Active client count | Alert if exceeds capacity planning |
$SYS/broker/load/messages/sent/1min | Message throughput | Alert on sudden drops (potential issue) |
$SYS/broker/uptime | Broker uptime | Alert on unexpected restarts |
$SYS/broker/heap/current | Memory usage | Alert 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:
| Service | CPU Limit | Memory Limit | Rationale |
|---|---|---|---|
mosquitto | 2.0 CPUs | 2GB | Message processing and retained message storage |
cloudflared | 1.0 CPU | 512MB | Lightweight 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:
| Parameter | Default | Production Recommendation | Purpose |
|---|---|---|---|
max_connections | -1 (unlimited) | Set based on capacity | Prevent resource exhaustion |
max_inflight_messages | 20 | 100-1000 for QoS > 0 | Increase throughput for reliable messaging |
max_queued_messages | 1000 | 10000-100000 | Buffer for slow consumers |
max_packet_size | 0 (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 Dimension | Approach | Considerations |
|---|---|---|
| Vertical | Increase resource limits in docker-compose.yml | Limited by host capacity; simplest approach |
| Horizontal (Clients) | Deploy multiple cloudflared replicas | Cloudflare handles load distribution |
| Horizontal (Brokers) | Bridge multiple Mosquitto instances | Requires shared storage or message replication |
| Geographic | Deploy instances in multiple regions with bridges | Reduces 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:latestcloudflare/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:
| Image | Version Selection | Rationale |
|---|---|---|
eclipse-mosquitto | Use specific minor version (e.g., 2.0.18) | Ensures stable MQTT protocol implementation |
cloudflare/cloudflared | Use 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:
- Check Release Notes : Review changelogs for both Mosquitto and cloudflared
- Test in Non-Production : Deploy new versions in a test environment
- Backup Current State : Backup data directory and configuration files
- Schedule Maintenance Window : Notify users of planned downtime
- Update docker-compose.yml : Change image tags to new versions
- Pull New Images :
docker compose pull - Restart Services :
docker compose down && docker compose up -d - Verify Operation : Check logs with
docker compose logs -f - Monitor Metrics : Watch
$SYStopics for anomalies
Rollback Procedure:
If issues arise after update:
- Stop services:
docker compose down - Revert image tags in docker-compose.yml:5-12 to previous versions
- Restore data directory from backup if necessary
- 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 Case | Command | Expected Result |
|---|---|---|
| Subscribe to topic | mosquitto_sub -h hostname -t test/# | Connection established |
| Publish message | mosquitto_pub -h hostname -t test/msg -m "test" | Message received by subscribers |
| Retained message | mosquitto_pub -h hostname -t test/retained -m "data" -r | Message persisted and delivered to new subscribers |
| QoS 1 delivery | mosquitto_pub -h hostname -t test/qos1 -m "data" -q 1 | PUBACK received |
| QoS 2 delivery | mosquitto_pub -h hostname -t test/qos2 -m "data" -q 2 | PUBREC/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:
- Test anonymous access is disabled (if authentication is configured)
- Test ACL enforcement (if ACL is configured)
- Test persistence by publishing retained messages and restarting the container
- 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:
| Solution | Integration Approach | Use Case |
|---|---|---|
| HashiCorp Vault | Vault agent sidecar or init container | Multi-service deployments |
| AWS Secrets Manager | ECS task execution role | AWS-hosted deployments |
| Azure Key Vault | Managed identity | Azure-hosted deployments |
| Google Secret Manager | Workload identity | GCP-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 Level | RTO Target | Required Preparations |
|---|---|---|
| Critical (Data Loss) | 1 hour | Automated backup restoration scripts |
| Major (Service Down) | 4 hours | Documented recovery procedures |
| Minor (Degraded) | 24 hours | Manual recovery acceptable |
Sources: docker-compose.yml:1-18
Recovery Point Objective (RPO)
Define acceptable data loss:
| Data Type | RPO Target | Backup Frequency |
|---|---|---|
| Retained messages | 5 minutes | Continuous with autosave_interval 300 |
| Configuration files | On change | Git commit |
| User credentials | On change | Backup before modification |
Sources: mosquitto.conf:1-6
Recovery Procedures
Complete System Recovery:
- Provision new Docker host
- Install Docker Engine and docker-compose
- Clone repository:
git clone https://github.com/jzombie/docker-mqtt-mosquitto-cloudflare-tunnel - Restore
.envfile from secure backup - Restore
mosquitto.conf, password file, and ACL file from backup - Restore data directory from most recent backup
- Execute
docker compose pullto download images - Execute
docker compose up -dto start services - Verify operation with connection tests
Data-Only Recovery:
- Stop services:
docker compose down - Restore data directory from backup
- Start services:
docker compose up -d - Verify retained messages are accessible
Sources: docker-compose.yml:1-18
Testing Recovery Procedures
Quarterly disaster recovery tests validate recovery procedures:
- Document current system state
- Destroy test environment
- Execute recovery procedures
- Validate data integrity
- Measure actual RTO and RPO
- 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:
| Plan | Cost | Tunnel Limit | Use Case |
|---|---|---|---|
| Free | $0/month | 1 user | Development, small deployments |
| Zero Trust Standard | $7/user/month | Up to 50 seats | Small teams |
| Zero Trust Enterprise | Custom | Unlimited | Large 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:
- Deploy with conservative limits
- Monitor actual resource consumption with
docker stats - Adjust limits to provide 20-30% headroom above peak usage
- 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 Type | Retention Period | Deletion Method |
|---|---|---|
| MQTT broker logs | 90 days | Automated log rotation |
| Retained messages | Based on business requirements | Manual or automated cleanup |
| Audit logs | 7 years (for some regulations) | Archive to cold storage |
Sources: mosquitto.conf:1-6
Privacy Considerations
For GDPR or similar privacy regulations:
- Implement user data isolation using ACLs (see Topic Access Control))
- Document message content and retention in privacy policy
- Implement data deletion procedures for retained messages
- Encrypt messages at rest (see Encrypted Retained Messages)
- 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:
- Security: Disable anonymous access (mosquitto.conf2), implement authentication and ACLs
- Persistence: Add data volume mounts to docker-compose.yml:4-9 for retained messages
- Monitoring: Configure logging, metrics collection, and alerting
- Resource Management: Define resource limits in docker-compose.yml:4-9
- High Availability: Deploy multiple tunnel replicas or broker instances with bridging
- Maintenance: Pin image versions in docker-compose.yml:5-12 and establish update procedures
- Testing: Implement health checks and load testing
- 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
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:
| File | Purpose | Version Controlled | Contains Secrets |
|---|---|---|---|
docker-compose.yml | Service orchestration and container definitions | Yes | No |
mosquitto.conf | MQTT broker configuration (listeners, protocols, access control) | Yes | No |
.env | Runtime secrets and environment variables | No | Yes |
.env.sample | Template for .env file | Yes | No |
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 Aspect | Source File | Line(s) | Applies To | Effect |
|---|---|---|---|---|
| MQTT TCP listener | mosquitto.conf | 1 | mosquitto service | Opens port 1883 for MQTT TCP connections |
| Anonymous access | mosquitto.conf | 2 | mosquitto service | Allows connections without authentication |
| WebSocket listener | mosquitto.conf | 4-5 | mosquitto service | Opens port 9001 for WebSocket MQTT connections |
| Mosquitto image | docker-compose.yml | 5 | mosquitto service | Uses eclipse-mosquitto:latest |
| Mosquitto container name | docker-compose.yml | 6 | mosquitto service | Container named mosquitto |
| Configuration mount | docker-compose.yml | 8 | mosquitto service | Mounts mosquitto.conf to /mosquitto/config/mosquitto.conf |
| Mosquitto restart policy | docker-compose.yml | 9 | mosquitto service | Restarts unless explicitly stopped |
| Cloudflared image | docker-compose.yml | 12 | cloudflared service | Uses cloudflare/cloudflared:latest |
| Cloudflared container name | docker-compose.yml | 13 | cloudflared service | Container named cloudflared |
| Tunnel command | docker-compose.yml | 14 | cloudflared service | Runs tunnel --no-autoupdate run --token |
| Cloudflared restart policy | docker-compose.yml | 15 | cloudflared service | Restarts unless explicitly stopped |
| Tunnel token | .env | 1 | cloudflared service | Authenticates 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 Stage | Component | What Is Validated | Failure Behavior |
|---|---|---|---|
| Compose file parse | docker-compose CLI | YAML syntax of docker-compose.yml | Exit with parse error |
| Environment variable resolution | docker-compose CLI | Presence of ${CLOUDFLARE_TUNNEL_TOKEN} in .env | Warning or substitution with empty string |
| Mosquitto config parse | mosquitto process | Syntax of mosquitto.conf directives | Container logs error and may exit |
| Tunnel authentication | cloudflared process | Validity of CLOUDFLARE_TUNNEL_TOKEN | Connection fails, logged in container output |
| Port binding | Docker Engine | Availability of specified ports | Container 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:
- Command-line arguments to
docker-compose(highest precedence) - Environment variables in the shell running
docker-compose .envfile in the project directory- Default values in
docker-compose.yml - 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 Name | Configuration File | Line Number(s) |
|---|---|---|
version | docker-compose.yml | 1 |
services.mosquitto.image | docker-compose.yml | 5 |
services.mosquitto.container_name | docker-compose.yml | 6 |
services.mosquitto.volumes | docker-compose.yml | 7-8 |
services.mosquitto.restart | docker-compose.yml | 9 |
services.cloudflared.image | docker-compose.yml | 12 |
services.cloudflared.container_name | docker-compose.yml | 13 |
services.cloudflared.command | docker-compose.yml | 14 |
services.cloudflared.restart | docker-compose.yml | 15 |
services.cloudflared.environment | docker-compose.yml | 16-17 |
listener (port 1883) | mosquitto.conf | 1 |
allow_anonymous | mosquitto.conf | 2 |
listener (port 9001) | mosquitto.conf | 4 |
protocol | mosquitto.conf | 5 |
CLOUDFLARE_TUNNEL_TOKEN | .env.sample (template) | 1 |
Sources: docker-compose.yml:1-18 mosquitto.conf:1-6 .env.sample:1-3
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
| Property | Value | Line |
|---|---|---|
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
| Property | Value | Line | Description |
|---|---|---|---|
image | eclipse-mosquitto:latest | docker-compose.yml5 | Official Eclipse Mosquitto Docker image, latest version |
container_name | mosquitto | docker-compose.yml6 | Fixed container name for internal DNS resolution |
volumes | ./mosquitto.conf:/mosquitto/config/mosquitto.conf | docker-compose.yml:7-8 | Mounts local config file into container |
restart | unless-stopped | docker-compose.yml9 | Automatic 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:
- Provides predictable DNS resolution within Docker's internal network
- Enables the
cloudflaredservice to reference the broker atmosquitto: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
| Component | Value | Description |
|---|---|---|
| Host Path | ./mosquitto.conf | Relative path from docker-compose.yml location |
| Container Path | /mosquitto/config/mosquitto.conf | Standard Mosquitto config location |
| Mount Type | Bind mount | Direct 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
| Property | Value | Line | Description |
|---|---|---|---|
image | cloudflare/cloudflared:latest | docker-compose.yml12 | Official Cloudflare Tunnel client image |
container_name | cloudflared | docker-compose.yml13 | Fixed container name |
command | tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN} | docker-compose.yml14 | Command with token interpolation |
restart | unless-stopped | docker-compose.yml15 | Automatic restart policy |
environment | CLOUDFLARE_TUNNEL_TOKEN | docker-compose.yml:16-17 | Environment 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}
| Argument | Purpose |
|---|---|
tunnel | Main cloudflared subcommand for tunnel operations |
--no-autoupdate | Disables automatic updates within container (container restart handles updates) |
run | Starts the tunnel using token authentication |
--token | Specifies 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
| Scenario | Behavior |
|---|---|
| Container exits with error | Automatically restarts |
| Container exits successfully (exit code 0) | Automatically restarts |
| Docker daemon restarts | Container restarts |
Manual docker stop | Container remains stopped |
Manual docker-compose down | Container remains stopped |
| System reboot | Container does NOT restart (requires Docker to start stopped containers) |
Alternative Restart Policies
While this system uses unless-stopped, Docker Compose supports other policies:
| Policy | Description | Use Case |
|---|---|---|
no | Never restart (default) | Development, debugging |
always | Always restart, even after manual stop | Maximum availability |
unless-stopped | Restart unless explicitly stopped | Recommended for most services |
on-failure[:max-retries] | Restart only on non-zero exit | Services 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
| Property | Type | Required | Value in File |
|---|---|---|---|
version | string | No (but recommended) | '3.8' |
services | object | Yes | Contains mosquitto and cloudflared |
Mosquitto Service Properties
| Property Path | Type | Required | Value | Default if Omitted |
|---|---|---|---|---|
services.mosquitto.image | string | Yes | eclipse-mosquitto:latest | N/A |
services.mosquitto.container_name | string | No | mosquitto | Auto-generated |
services.mosquitto.volumes | array | No | ["./mosquitto.conf:/mosquitto/config/mosquitto.conf"] | No volumes |
services.mosquitto.restart | string | No | unless-stopped | no |
Cloudflared Service Properties
| Property Path | Type | Required | Value | Default if Omitted |
|---|---|---|---|---|
services.cloudflared.image | string | Yes | cloudflare/cloudflared:latest | N/A |
services.cloudflared.container_name | string | No | cloudflared | Auto-generated |
services.cloudflared.command | string/array | No | tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN} | Image default |
services.cloudflared.restart | string | No | unless-stopped | no |
services.cloudflared.environment | array/object | No | ["CLOUDFLARE_TUNNEL_TOKEN"] | No environment vars |
Sources: docker-compose.yml:1-18
Deployment Commands
The following commands operate on this docker-compose.yml file:
| Command | Purpose |
|---|---|
docker-compose up -d | Start all services in detached mode |
docker-compose down | Stop and remove all containers |
docker-compose ps | Show service status |
docker-compose logs | View combined logs |
docker-compose logs mosquitto | View mosquitto logs only |
docker-compose logs cloudflared | View cloudflared logs only |
docker-compose restart | Restart all services |
docker-compose pull | Pull 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
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:
| Parameter | Required | Description |
|---|---|---|
port | Yes | TCP port number (1-65535) on which to listen for connections |
bind_address | No | IP address or hostname to bind to (default: all interfaces) |
Behavior:
- Multiple
listenerdirectives create multiple independent listeners - Each listener can be configured with different protocols and security settings
- Configuration directives following a
listenerapply to that specific listener until the nextlistenerdirective - 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:
| Parameter | Required | Valid Values | Description |
|---|---|---|---|
| Setting | Yes | true or false | Enable 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:
| Parameter | Required | Valid Values | Description |
|---|---|---|---|
protocol_type | Yes | mqtt, websockets | Protocol encapsulation method |
Behavior:
- Must be specified within a listener block (after a
listenerdirective) - 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:
| Value | Description | Use Case |
|---|---|---|
mqtt | Standard MQTT over TCP | Traditional MQTT clients, IoT devices, backend services |
websockets | MQTT over WebSocket protocol | Web 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
| Directive | Purpose |
|---|---|
password_file | Path to file containing username/password credentials |
acl_file | Path to file defining topic-based access control rules |
allow_zero_length_clientid | Control client ID requirements |
TLS/SSL Configuration
| Directive | Purpose |
|---|---|
cafile | CA certificate for client certificate validation |
certfile | Server certificate for TLS |
keyfile | Server private key for TLS |
require_certificate | Require client certificates |
Persistence and Logging
| Directive | Purpose |
|---|---|
persistence | Enable message persistence to disk |
persistence_location | Directory for persistence database |
log_dest | Logging destination (file, stdout, syslog) |
log_type | Types of messages to log |
Connection Limits
| Directive | Purpose |
|---|---|
max_connections | Maximum concurrent client connections |
max_keepalive | Maximum keepalive interval |
max_packet_size | Maximum 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
-
Edit
mosquitto.confin the project root directory -
Restart the mosquitto container to apply changes:
-
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
| Rule | Description |
|---|---|
| Line-based | Each directive appears on a separate line |
| Comments | Lines starting with # are ignored |
| Whitespace | Leading and trailing whitespace is ignored |
| Blank lines | Empty lines are ignored |
| Case sensitivity | Directive names are case-sensitive |
| Continuation | No 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
listenerdirectives
Listener-Specific Directives:
- Apply only to the immediately preceding
listener - Examples:
protocol,cafile,certfile - Must follow a
listenerdirective
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
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 Name | Required | Used By | Purpose | Format |
|---|---|---|---|---|
CLOUDFLARE_TUNNEL_TOKEN | Yes | cloudflared service | Authenticates the cloudflared tunnel client with Cloudflare's network | Base64-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:
-
Command Line Substitution (docker-compose.yml14): The token is interpolated directly into the
cloudflaredcommand:command: tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN} -
Environment Variable Declaration (docker-compose.yml:16-17): The variable is also declared in the service's
environmentsection, 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
.envfile containing actual tokens is excluded by.gitignore - Access control : Only authorized users should have access to the
.envfile 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:
| Symptom | Likely Cause | Solution |
|---|---|---|
| Container restarts continuously | Invalid or expired token | Verify token in Cloudflare dashboard, regenerate if needed |
| Error: "unable to authenticate" | Token not loaded from .env | Verify .env file exists in repository root and contains token |
| Error: "tunnel credentials file not found" | Token format incorrect | Ensure token is copied completely without line breaks or extra spaces |
| Empty token error | .env file not in correct location | Ensure .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:
- Shell environment variables (highest precedence)
.envfile in project directoryenvironmentsection indocker-compose.yml- 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:
- Read the
.envfile - Find the line
CLOUDFLARE_TUNNEL_TOKEN=<value> - Extract the value after the
=sign - 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:
- Add the variable to
.env.sampleas a template - Add the actual value to
.env(which remains untracked) - Reference it in
docker-compose.ymlusing${VARIABLE_NAME}syntax - Declare it in the service's
environmentsection if the container needs it as an environment variable
Sources: docker-compose.yml:1-18 .env.sample:1-3
Related Documentation
- Environment Variables : Step-by-step guide for creating and populating the
.envfile - Cloudflare Tunnel Configuration : How to obtain the tunnel token value
- Security Model : Security implications of credential management
- Version Control Practices : Explanation of
.gitignoreand secret protection
Sources: docker-compose.yml:1-18 .env.sample:1-3
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 ID | IMAGE | COMMAND | STATUS | NAMES |
|---|---|---|---|---|
| ... | eclipse-mosquitto:latest | ... | Up X minutes | mosquitto |
| ... | cloudflare/cloudflared:latest | ... | Up X minutes | cloudflared |
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 upresults in immediate exit - Error messages about missing files or invalid configuration
- Containers repeatedly restart
Common Causes :
| Error Pattern | Cause | Solution |
|---|---|---|
no configuration file provided | mosquitto.conf not found | Verify mosquitto.conf:1-6 exists in project root |
invalid or missing environment variable | .env file missing or malformed | Create .env from .env.sample, ensure CLOUDFLARE_TUNNEL_TOKEN is set |
Error response from daemon: pull access denied | Docker image unavailable | Check internet connectivity, verify image names in docker-compose.yml:5-12 |
Bind mount failed | Volume path incorrect | Verify volume path in docker-compose.yml:7-8 matches file location |
Mosquitto Container Exits Immediately
Diagnostic Steps :
-
Check mosquitto logs:
-
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 :
-
Check cloudflared logs:
-
Identify error type:
| Log Message | Issue | Solution |
|---|---|---|
failed to get tunnel | Invalid or missing CLOUDFLARE_TUNNEL_TOKEN | Verify token in .env file matches Cloudflare dashboard |
failed to dial Cloudflare edge | Network connectivity issue | Check internet connection, firewall rules |
unauthorized: authentication failed | Token expired or revoked | Generate new token in Cloudflare Zero Trust dashboard |
no such host | DNS resolution failure | Check DNS configuration, network settings |
Token Configuration Issues :
The environment variable must be correctly passed from .env to the container. Verify:
.envfile exists in project root with format:
CLOUDFLARE_TUNNEL_TOKEN=your_actual_token_here
- The token is referenced in docker-compose.yml14 as
${CLOUDFLARE_TUNNEL_TOKEN} - 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 :
cloudflaredcontainer runs but clients cannot connect- Logs show repeated connection attempts
- Cloudflare dashboard shows tunnel as "Inactive"
Diagnostic Commands :
Common Issues :
-
Token Mismatch : The token in
.envdoesn't match the tunnel configuration- Solution: Copy exact token from Cloudflare dashboard as shown in README.md:47-51
- Recreate
.envfile with correct format
-
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
-
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
- Solution: Create new tunnel in Cloudflare Zero Trust, update token in
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 :
-
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)
-
Test internal routing:
Common Configuration Errors :
| Dashboard Setting | Incorrect Value | Correct Value | Impact |
|---|---|---|---|
| Service Type | TCP/SSH/RDP | HTTP | Protocol mismatch prevents connection |
| URL | mosquitto:1883 | mosquitto:9001 | Wrong port; port 9001 is WebSocket listener |
| URL | localhost:9001 | mosquitto:9001 | DNS resolution fails; use container name |
| Protocol | HTTPS | HTTP | Unnecessary 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:
- Use the public hostname configured in Cloudflare Zero Trust (e.g.,
mqtt.example.com) - Use WebSocket protocol when connecting through the tunnel
- Use standard port (80 for HTTP, 443 for HTTPS) - do not specify 9001
- Not require TLS at the MQTT level (Cloudflare provides TLS termination)
Example Client Configurations :
| Client Type | Connection String | Protocol | Notes |
|---|---|---|---|
| Mosquitto CLI | mosquitto_pub -h mqtt.example.com -t test | MQTT over TCP | Will fail; use WebSocket client instead |
| JavaScript MQTT.js | ws://mqtt.example.com or wss://mqtt.example.com | WebSocket | Recommended |
| Python Paho MQTT | Transport: websockets | WebSocket | Set transport explicitly |
| Mobile Apps | Depends on library | WebSocket | Check library supports WebSocket transport |
Common Mistakes :
-
Specifying Port 9001 : Clients should not include
:9001in the hostname. Cloudflare listens on standard ports and routes internally. -
Using Standard MQTT Instead of WebSocket : Port 9001 is configured with
protocol websocketsin mosquitto.conf5 Standard MQTT clients will fail. -
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 :
-
For External Clients (through Cloudflare) :
- Must use WebSocket-capable MQTT libraries
- Connection URL format:
ws://orwss://(notmqtt://) - The Cloudflare tunnel routes to port 9001 which requires WebSocket protocol
-
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:
| Error | Symptom | Fix |
|---|---|---|
| Missing listener port | Error: Empty listener statement | Ensure listener is followed by port number |
| Protocol on wrong listener | Unexpected protocol behavior | protocol websockets must follow listener 9001, not listener 1883 |
| Typo in directive | Error: Unknown configuration variable | Check spelling of directives like allow_anonymous |
| Extra characters | Parse errors | No 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 :
- Stop conflicting service
- Change mosquitto listener ports in
mosquitto.conf - If changing ports, update Cloudflare public hostname URL accordingly
Issue: WebSocket Listener Not Working
If WebSocket clients cannot connect but standard MQTT clients can:
- Verify
protocol websocketsdirective exists at mosquitto.conf5 - Confirm it's associated with the correct listener (9001)
- 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 :
-
Invalid mosquitto.conf :
- Syntax errors prevent broker startup
- Solution: Test locally with
docker compose up mosquitto
-
Missing mosquitto.conf :
- File not committed to repository
- Check
.gitignoredoesn't exclude it
-
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 :
-
Check Image Pull Time : First run pulls
eclipse-mosquitto:latest, which may take time- Not typically an issue as GitHub runners have good bandwidth
-
Check Container Logs :
Look for configuration errors or startup failures
-
Check System Resources : Insufficient memory/CPU could slow startup
- Mosquitto is lightweight; this is rarely the issue
-
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 :
| Service | Log Message | Meaning | Action |
|---|---|---|---|
| mosquitto | Opening ipv4 listen socket on port 1883 | Listener 1883 started successfully | Normal |
| mosquitto | Opening websockets listen socket on port 9001 | Listener 9001 started successfully | Normal |
| mosquitto | Error: Unable to open config file | Config file missing or inaccessible | Check docker-compose.yml:7-8 volume mount |
| mosquitto | New connection from | Client connected | Normal |
| cloudflared | Connection established | Tunnel connected to Cloudflare edge | Normal |
| cloudflared | failed to get tunnel | Token invalid | Update .env with correct token |
| cloudflared | Retrying connection | Temporary connection issue | Monitor; if persists, check network |
Sources : docker-compose.yml:4-17, mosquitto.conf:1-6
Container Inspection
Detailed Container State :
Common Inspection Checks :
-
Restart Count : High restart count indicates recurring failures
-
Environment Variables : Verify cloudflared receives token
-
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 :
- Containers on Different Networks : Should both be on default compose network
- DNS Resolution Failure : Container name
mosquittodoesn't resolve - Firewall Within Container : Unlikely with standard images
Sources : docker-compose.yml:1-18
Environment Variable Issues
.env File Problems
Verification Checklist :
Common .env Mistakes :
- Spaces Around Equals :
KEY = value(incorrect) vsKEY=value(correct) - Quotes : Not needed unless value contains spaces
- Comments : Use
#at start of line, not inline - File Name : Must be exactly
.env, notenvor.env.txt - Encoding : Must be UTF-8, not UTF-16 or other encodings
- 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:
-
Truncated Token : Token copied incompletely from dashboard
- Tokens are typically 200+ characters
- Verify entire token was copied
-
Corrupted Token : Extra characters or line breaks inserted
- Token should be single line with no spaces
- Common when copying from certain terminals
-
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
- Token should start with
Sources : README.md:47-51, docker-compose.yml:14-17
Additional Resources
For ongoing issues not covered in this troubleshooting guide:
- Mosquitto Documentation : https://mosquitto.org/documentation/
- Cloudflare Tunnel Documentation : https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/
- Docker Compose Documentation : https://docs.docker.com/compose/
For advanced configuration topics:
- Topic-based access control: See Topic Access Control (ACL))
- Production deployment: See Production Considerations
- Security hardening: See Security Model
Sources : README.md:15-21, README.md:5-11