This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Cloudflared Tunnel Connector
Loading…
Cloudflared Tunnel Connector
Relevant source files
Purpose and Scope
This document details the cloudflared container component, which acts as the secure tunnel connector between the Cloudflare network and the local Mosquitto MQTT broker. It explains the container’s configuration, how it authenticates with Cloudflare’s infrastructure, how it establishes and maintains the tunnel connection, and how it routes incoming traffic to the Mosquitto service.
For broader system architecture context, see System Architecture. For Docker Compose orchestration details, see Docker Compose Orchestration. For environment variable configuration, see Environment Variables.
Container Overview
The cloudflared service is defined in docker-compose.yml:11-17 and runs the official Cloudflare Tunnel connector image. This container establishes an outbound-only persistent connection to Cloudflare’s network, eliminating the need for open inbound ports on the host system.
Container Specification
| Property | Value | Purpose |
|---|---|---|
| Service Name | cloudflared | Docker Compose service identifier |
| Container Name | cloudflared | Runtime container name |
| Image | cloudflare/cloudflared:latest | Official Cloudflare tunnel connector |
| Command | tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN} | Tunnel execution with token authentication |
| Restart Policy | unless-stopped | Automatic restart except on manual stop |
| Environment Variables | CLOUDFLARE_TUNNEL_TOKEN | Tunnel authentication credential |
Sources: docker-compose.yml:11-17
Container Configuration Architecture
The following diagram maps the Docker Compose configuration elements to their runtime purposes:
Sources: docker-compose.yml:11-17
graph TB
subgraph "docker-compose.yml Definition"
SERVICE["services.cloudflared"]
IMAGE["image: cloudflare/cloudflared:latest"]
CONTAINER["container_name: cloudflared"]
COMMAND["command: tunnel --no-autoupdate run --token"]
RESTART["restart: unless-stopped"]
ENV_SECTION["environment:\n- CLOUDFLARE_TUNNEL_TOKEN"]
end
subgraph "Runtime Configuration"
TOKEN_VAR["${CLOUDFLARE_TUNNEL_TOKEN}\nfrom .env file"]
CMD_EXEC["Executed Command:\ntunnel --no-autoupdate run --token <actual_token>"]
end
subgraph "Operational Behavior"
CONTAINER_PROC["cloudflared Process"]
AUTO_RESTART["Automatic Restart\non Failure"]
NO_UPDATE["Update Mechanism\nDisabled"]
end
SERVICE --> IMAGE
SERVICE --> CONTAINER
SERVICE --> COMMAND
SERVICE --> RESTART
SERVICE --> ENV_SECTION
ENV_SECTION --> TOKEN_VAR
COMMAND --> TOKEN_VAR
TOKEN_VAR --> CMD_EXEC
CMD_EXEC --> CONTAINER_PROC
RESTART --> AUTO_RESTART
COMMAND --> NO_UPDATE
CONTAINER_PROC --> NO_UPDATE
CONTAINER_PROC --> AUTO_RESTART
Tunnel Authentication
The cloudflared connector authenticates with Cloudflare’s infrastructure using a cryptographically secure tunnel token. This token is generated during the Cloudflare Tunnel creation process and links the local connector to a specific tunnel configuration on Cloudflare’s network.
sequenceDiagram
participant User as "Administrator"
participant CF_UI as "Cloudflare Dashboard"
participant Env as ".env File"
participant Compose as "docker-compose"
participant CFD as "cloudflared Container"
participant CF_Net as "Cloudflare Network"
User->>CF_UI: Create tunnel via Zero Trust dashboard
CF_UI->>CF_UI: Generate CLOUDFLARE_TUNNEL_TOKEN
CF_UI-->>User: Display token in connector setup
User->>Env: Create .env file with token
Note over Env: CLOUDFLARE_TUNNEL_TOKEN=eyJh...
User->>Compose: docker compose up
Compose->>Env: Read CLOUDFLARE_TUNNEL_TOKEN
Compose->>CFD: Start container with token in command
Note over CFD: tunnel run --token <token>
CFD->>CF_Net: Initiate tunnel connection with token
CF_Net->>CF_Net: Validate token signature
CF_Net->>CF_Net: Retrieve tunnel configuration
CF_Net-->>CFD: Tunnel established
Note over CFD,CF_Net: Persistent connection maintained
Token Lifecycle
Token Security Properties
- Single-Use Binding: Each token is bound to a specific tunnel UUID in Cloudflare’s infrastructure
- Environment Injection: Token is injected via environment variable substitution at container start
- Never Logged: The
--tokenflag ensures the token does not appear in process listings - Git Exclusion: The
.envfile containing the token is excluded via.gitignore
Sources: docker-compose.yml14 docker-compose.yml:16-17 .env.sample1 README.md53
Connection Establishment
The cloudflared container establishes an outbound-only connection to Cloudflare’s edge network. This inverted architecture eliminates the need for inbound firewall rules or public IP addresses.
Tunnel Command Structure
The command executed by the container is:
tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}
| Flag | Purpose |
|---|---|
tunnel | Primary cloudflared operation mode |
--no-autoupdate | Disables automatic binary updates to ensure container image control |
run | Executes the tunnel connector |
--token | Provides authentication credential inline |
Sources: docker-compose.yml14
Connection Flow
Sources: docker-compose.yml14 README.md64 README.md67
Traffic Routing Mechanism
The cloudflared container acts as a local reverse proxy, receiving traffic from the Cloudflare tunnel and forwarding it to the mosquitto service using Docker’s internal DNS resolution.
graph LR
subgraph "Cloudflare Configuration"
CF_HOST["Public Hostname:\nmqtt.example.com"]
CF_SERVICE["Service Type: HTTP"]
CF_URL["URL: mosquitto:9001"]
end
subgraph "Docker Network"
DNS["Docker Internal DNS"]
MOSQ_NAME["Container Name:\nmosquitto"]
MOSQ_IP["Container IP:\n172.x.x.x"]
end
subgraph "cloudflared Container"
CFD_PROC["cloudflared Process"]
RESOLVER["DNS Resolver"]
HTTP_CLIENT["HTTP Client"]
end
CF_HOST --> CF_SERVICE
CF_SERVICE --> CF_URL
CF_URL -.->|'mosquitto' hostname configured in tunnel| CFD_PROC
CFD_PROC --> RESOLVER
RESOLVER --> DNS
DNS --> MOSQ_NAME
MOSQ_NAME --> MOSQ_IP
MOSQ_IP -.-> HTTP_CLIENT
HTTP_CLIENT -->|HTTP to 172.x.x.x:9001| MOSQ_IP
Internal DNS Resolution
Routing Configuration
The routing is configured in the Cloudflare Dashboard during tunnel setup:
- Public Hostname: User-defined subdomain (e.g.,
mqtt.example.com) - Service Type:
HTTP(internal protocol between cloudflared and mosquitto) - Service URL:
mosquitto:9001(Docker service name and port)
The mosquitto hostname in the service URL is resolved by Docker’s internal DNS to the IP address of the container named mosquitto as defined in docker-compose.yml6
Sources: README.md:62-67 docker-compose.yml:4-6
Protocol Translation
The cloudflared container performs protocol translation between external HTTPS/WSS connections and internal HTTP/WebSocket connections.
External vs Internal Protocols
| Direction | Protocol | Port | Encryption |
|---|---|---|---|
| Client → Cloudflare Edge | HTTPS/WSS | 443 | TLS (Cloudflare-managed) |
| Cloudflare Edge → cloudflared | Tunnel Protocol | N/A | Encrypted tunnel |
| cloudflared → mosquitto | HTTP/WebSocket | 9001 | Unencrypted (internal network) |
The external connection is encrypted end-to-end from client to Cloudflare’s edge, while the internal connection between cloudflared and mosquitto is unencrypted but isolated within Docker’s private bridge network.
Sources: README.md67 README.md82
Operational Characteristics
No Exposed Ports
The cloudflared container exposes no ports to the host system. All communication occurs via:
- Outbound tunnel connection to Cloudflare’s network
- Internal Docker network for communication with the mosquitto container
This design eliminates attack surface by ensuring no listening ports on the host machine.
Sources: docker-compose.yml:11-17
Restart Behavior
The container uses the unless-stopped restart policy defined in docker-compose.yml15 This ensures:
- Automatic recovery: Container restarts on crashes or Docker daemon restarts
- Persistent operation: Container continues running across reboots
- Manual control: Container only stops when explicitly commanded via
docker compose downordocker stop
Update Management
The --no-autoupdate flag in docker-compose.yml14 disables cloudflared’s built-in automatic update mechanism. This ensures:
- Version pinning: Updates are controlled via Docker image version
- Predictable behavior: No unexpected binary changes during operation
- Image-based updates: Updates occur through
docker compose pulland container recreation
Sources: docker-compose.yml:14-15
Connection State Diagram
Sources: docker-compose.yml:14-15
Environment Variable Configuration
The cloudflared container requires exactly one environment variable:
CLOUDFLARE_TUNNEL_TOKEN
- Source: Defined in
.envfile (see .env.sample1) - Injection Method: Environment variable substitution in docker-compose.yml14
- Format: Base64-encoded JSON containing tunnel credentials
- Scope: Container-level environment variable
- Security: Must not be committed to version control
The token is referenced twice in the configuration:
- In the
commandfield via shell substitution:${CLOUDFLARE_TUNNEL_TOKEN} - In the
environmentsection for explicit passing to the container
Sources: docker-compose.yml14 docker-compose.yml:16-17 .env.sample1
Security Model
Zero Inbound Exposure
The cloudflared container implements a security model where:
- No listening ports: Container binds no ports to the host interface
- Outbound-only connections: All connections are initiated from inside the network
- Tunnel-based access: External access is only possible through the authenticated tunnel
- No direct routes: No firewall rules or port forwarding required
Authentication Flow
Sources: docker-compose.yml14 docker-compose.yml:16-17 README.md53
Common Configuration Reference
Minimal Configuration
The cloudflared service requires minimal configuration:
Configuration Dependencies
| Dependency | Location | Purpose |
|---|---|---|
| Tunnel Token | .env file | Authentication credential |
| Tunnel Configuration | Cloudflare Dashboard | Routing and hostname mapping |
| Docker Network | Implicit (default bridge) | Container-to-container communication |
| mosquitto Service | docker-compose.yml:4-9 | Target service for traffic forwarding |
Sources: docker-compose.yml:11-17
Integration with Mosquitto
The cloudflared container integrates with the mosquitto container through Docker’s internal networking:
Service Discovery
- DNS Name:
mosquitto(from docker-compose.yml4) - Container Name:
mosquitto(from docker-compose.yml6) - Target Port:
9001(WebSocket listener) - Resolution: Automatic via Docker’s embedded DNS server
graph TB
subgraph "Host System"
HOST["No Exposed Ports"]
end
subgraph "Docker Bridge Network"
CFD_CONTAINER["cloudflared Container\nNo EXPOSE directive"]
MOSQ_CONTAINER["mosquitto Container\nNo EXPOSE directive"]
DOCKER_DNS["Docker DNS Server"]
end
subgraph "External Network"
CF_EDGE["Cloudflare Edge\nPublic HTTPS:443"]
end
CF_EDGE <-->|Outbound tunnel connection| CFD_CONTAINER
CFD_CONTAINER <-->|DNS query: mosquitto| DOCKER_DNS
DOCKER_DNS -->|Resolves to container IP| MOSQ_CONTAINER
CFD_CONTAINER -->|HTTP:9001| MOSQ_CONTAINER
HOST -.->|No direct connection| CFD_CONTAINER
HOST -.->|No direct connection| MOSQ_CONTAINER
Network Isolation
Both containers run on the same Docker bridge network without exposing ports to the host:
Sources: docker-compose.yml:4-6 docker-compose.yml:11-17 README.md64
Troubleshooting Connection Issues
Token Validation Failures
If the cloudflared container fails to establish a tunnel:
- Verify token format: Token should be a long base64-encoded string
- Check
.envfile: EnsureCLOUDFLARE_TUNNEL_TOKEN=<token>with no extra spaces - Confirm tunnel exists: Verify the tunnel is active in Cloudflare Dashboard
- Review logs:
docker logs cloudflaredwill show authentication errors
DNS Resolution Failures
If cloudflared cannot reach the mosquitto container:
- Verify container names: Both containers must use the names defined in docker-compose.yml
- Check network: Both containers must be on the same Docker network (default bridge)
- Confirm mosquitto is running:
docker psshould show both containers as “Up” - Test DNS resolution:
docker exec cloudflared nslookup mosquittoshould resolve
Service URL Configuration
The service URL in Cloudflare Dashboard must match the Docker configuration:
- Correct:
mosquitto:9001orhttp://mosquitto:9001 - Incorrect:
localhost:9001,127.0.0.1:9001,mqtt:9001
Sources: docker-compose.yml:4-6 README.md64 README.md84
Dismiss
Refresh this wiki
Enter email to refresh