Setting Up Cloudflare Tunnel with Docker Compose on macOS
Posted on August 29, 2025
Cloudflare Tunnel provides a secure way to expose your local services to the internet without opening ports on your firewall. When combined with Docker Compose on macOS, you can create a robust, containerized setup that's perfect for development, testing, or production deployments. In this guide, I'll show you how to set up and configure Cloudflare Tunnel using Docker Compose.
What is Cloudflare Tunnel?
Cloudflare Tunnel (formerly Argo Tunnel) creates an outbound-only connection to Cloudflare's edge network. This means:
- No open ports: Your local services remain secure behind your firewall
- Zero-trust security: Traffic is encrypted end-to-end
- Global CDN: Your services benefit from Cloudflare's global network
- DDoS protection: Built-in protection against attacks
Basic Docker Compose Setup
Here's the basic docker-compose.yml
configuration for Cloudflare Tunnel:
version: '3.8'
networks:
default:
name: cloudflared
driver: bridge
services:
cloudflared:
image: cloudflare/cloudflared:latest
command: tunnel --no-autoupdate run --token YOUR_TUNNEL_TOKEN
restart: unless-stopped
extra_hosts:
- "host.docker.internal:host-gateway"
Configuration Breakdown
Network Configuration
networks:
default:
name: cloudflared
driver: bridge
driver: bridge
: Creates a local bridge networkname: cloudflared
: Gives the default network a specific name for external access- Why this approach: Creates the network automatically while allowing other Docker Compose files to connect to it
Service Configuration
services:
cloudflared:
image: cloudflare/cloudflare/cloudflared:latest
command: tunnel --no-autoupdate run --token YOUR_TUNNEL_TOKEN
restart: unless-stopped
extra_hosts:
- "host.docker.internal:host-gateway"
image: cloudflare/cloudflared:latest
: Uses the official Cloudflare Tunnel Docker image--no-autoupdate
: Prevents automatic updates (useful for containerized environments)--token
: Your Cloudflare Tunnel token for authenticationrestart: unless-stopped
: Automatically restarts the container unless manually stoppedextra_hosts
: Mapshost.docker.internal
to the host machine (macOS-specific)
Setting Up Your Cloudflare Tunnel
Step 1: Create a Tunnel in Cloudflare Dashboard
- Log into your Cloudflare dashboard
- Navigate to Zero Trust → Access → Tunnels
- Click Create a tunnel
- Choose Cloudflared as the connector type
- Give your tunnel a name (e.g., "macos-docker-tunnel")
- Copy the tunnel token (you'll need this for the Docker Compose file)
Step 2: Create Your Docker Compose File
# docker-compose.yml
version: '3.8'
networks:
default:
name: cloudflared
driver: bridge
services:
cloudflared:
image: cloudflare/cloudflared:latest
command: tunnel --no-autoupdate run --token YOUR_ACTUAL_TOKEN_HERE
restart: unless-stopped
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
- TZ=Europe/Amsterdam
volumes:
- ./cloudflared:/etc/cloudflared
Note: This configuration automatically creates a network named cloudflared
that other Docker Compose files can connect to using external: true
.
Step 3: Start the Tunnel
# Start the Cloudflare Tunnel
docker-compose up -d
# Check the logs
docker-compose logs -f cloudflared
Advanced Configuration Examples
Example 1:
# docker-compose.yml
# MUST create network, if not exist: docker network create cloudflared
networks:
default:
external: true
name: cloudflared
services:
whoami:
image: "traefik/whoami"
volumes:
- ./data/ssl:/ssl
Uses default network for all services as
cloudflared
- or -
# docker-compose.yml
# MUST create network, if not exist: docker network create cloudflared
networks:
cloudflared:
external: true
services:
whoami:
image: "traefik/whoami"
networks:
- cloudflared
volumes:
- ./data/ssl:/ssl
Using
cloudflared
existing network
In Cloudflared Public hostnames:
- Public hostname:
whoami.example.com
- Path:
*
- Service:
http://whoami:80
- Origin configurations:
0
Example 2: Local Development Services
When you have local development servers running on your Mac:
# Vite development server
pnpm dev # Runs on http://localhost:5173
# Nuxt development server
npm run dev # Runs on http://localhost:3000
In Cloudflare Dashboard → Public Hostnames:
For Vite:
- Public hostname:
vite.example.com
- Path:
*
- Service:
http://host.docker.internal:5173
- Origin configurations:
0
For Nuxt:
- Public hostname:
nuxt.example.com
- Path:
*
- Service:
http://host.docker.internal:3000
- Origin configurations:
0
Example 3: Multiple Docker Services
# docker-compose.yml
networks:
default:
external: true
name: cloudflared
services:
webapp:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./webapp:/usr/share/nginx/html
api:
image: node:18-alpine
ports:
- "3000:3000"
volumes:
- ./api:/app
working_dir: /app
command: npm start
In Cloudflare Dashboard → Public Hostnames:
For Web App:
- Public hostname:
webapp.example.com
- Path:
*
- Service:
http://webapp:80
- Origin configurations:
0
For API:
- Public hostname:
api.example.com
- Path:
*
- Service:
http://api:3000
- Origin configurations:
0
Configuring Routes in Cloudflare Dashboard
After your tunnel is running, configure routes in the Cloudflare dashboard:
Step 1: Access Tunnel Configuration
- Go to Zero Trust → Access → Tunnels
- Click on your tunnel name
- Go to the Public Hostnames tab
Step 2: Add Public Hostnames
For each service you want to expose:
Subdomain: your-subdomain
Domain: yourdomain.com
Service: http://service-name:port (for Docker services)
Service: http://host.docker.internal:port (for local services)
Step 3: Configure Settings
- Type: HTTP
- Path:
*
(for all paths) - Origin configurations:
0
(no additional settings)
Troubleshooting
Check if Tunnel is Running
# Check container status
docker-compose ps
# View logs
docker-compose logs cloudflared
# Check network
docker network ls | grep cloudflared
Test Connectivity
# Test from within container
docker-compose exec cloudflared ping host.docker.internal
# Test local service
docker-compose exec cloudflared curl http://host.docker.internal:5173
Common Issues
Issue: "Network not found"
# Create the network manually
docker network create cloudflared
Issue: "Service not accessible"
# Check if service is running on host
curl http://localhost:5173
# Verify network connectivity
docker network inspect cloudflared
Security Tips
Use Environment Variables
# docker-compose.yml
services:
cloudflared:
image: cloudflare/cloudflared:latest
command: tunnel --no-autoupdate run --token ${CLOUDFLARE_TOKEN}
environment:
- CLOUDFLARE_TOKEN=${CLOUDFLARE_TOKEN}
# .env file
CLOUDFLARE_TOKEN=your_actual_token_here
Access Policies (Optional)
In Cloudflare Dashboard:
- Go to Zero Trust → Access → Applications
- Create policies to restrict access to your services
- Add authentication if needed
Quick Start Checklist
- ✅ Create tunnel in Cloudflare dashboard
- ✅ Copy tunnel token
- ✅ Create
docker-compose.yml
with your token - ✅ Start tunnel:
docker-compose up -d
- ✅ Add public hostnames in Cloudflare dashboard
- ✅ Test your services
Conclusion
Cloudflare Tunnel with Docker Compose on macOS provides a simple, secure way to expose your local services. The key benefits:
- Simple setup: Just a few lines of YAML
- Secure: No open ports, encrypted traffic
- Flexible: Works with local services and Docker containers
- Reliable: Automatic restarts and health monitoring
This approach is perfect for development, testing, and sharing your work securely with others!