OCI Artifact - Deploy directly from GitHub Container Registry
Secure tunnel connector that routes traffic from Cloudflare's edge network to your services without exposing ports to the public internet. Enables zero-trust access via Cloudflare WARP and Access policies.
This is a Docker Compose OCI artifact, not a traditional Docker image. It contains a complete docker-compose.yml configuration that you can deploy directly using Docker 25.0+.
Cloudflare Tunnel creates an encrypted outbound-only connection from your infrastructure to Cloudflare's network. This eliminates the need to expose ports 80/443 to the internet, providing:
- Zero-trust security: Services hidden from public internet
- DDoS protection: Cloudflare absorbs attacks at the edge
- Access control: Require WARP client or identity verification
- No firewall rules: Outbound-only connections, no port forwarding needed
┌─────────────────────────────────────────────────────────────────┐
│ CLOUDFLARE EDGE │
├─────────────────────────────────────────────────────────────────┤
│ Public Hostnames (configured in Dashboard) │
│ │ │
│ ├─► Access Policy: "Allow All" ──► Public Services │
│ │ │
│ └─► Access Policy: "WARP Only" ──► Protected Services │
│ (requires enrolled device) │
└────────────────────────────┬────────────────────────────────────┘
│ Encrypted Tunnel
▼
┌────────────────────────────────────────────────────────────────┐
│ DOCKER HOST │
│ │
│ ┌─────────────┐ http://traefik:80 ┌──────────────────────┐ │
│ │ cloudflared │────────────────────►│ Traefik │ │
│ │ container │ │ (label discovery) │ │
│ └─────────────┘ └──────────┬───────────┘ │
│ │ │
│ ┌────────────────────────────────────────┤ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌───────────┐ ┌───────────┐ │
│ │ GitLab │ │ Metabase │ ... │ Service │ │
│ └─────────┘ └───────────┘ └───────────┘ │
│ │
│ Network: traefik_default │
└────────────────────────────────────────────────────────────────┘
- Go to Cloudflare Zero Trust Dashboard
- Navigate to Networks → Connectors
- Click Create a connector → Cloudflared
- Name your tunnel (e.g.,
beecompose-production) - Copy the tunnel token from the install command
In the connector configuration, add Published application routes:
| Public Hostname | Service | Notes |
|---|---|---|
*.example.com |
http://traefik:80 |
Wildcard routes all to Traefik |
Or configure per-service for granular Access policies:
| Public Hostname | Service | Access Policy |
|---|---|---|
gitlab.example.com |
http://traefik:80 |
Allow All |
admin.example.com |
http://traefik:80 |
Require WARP |
*.example.com |
http://traefik:80 |
Bypass (catch-all) |
# 1. Create environment file
cat > .env.cloudflared << 'EOF'
COMPOSE_PROJECT_NAME=cloudflared
CF_TUNNEL_TOKEN=eyJhIjoiYWJjZGVmMTIzNDU2Nzg5MCIsInQiOiJ5b3VyLXR1bm5lbC1pZCIsInMiOiJ5b3VyLXNlY3JldCJ9
EOF
# 2. Deploy
bc cloudflared up
# 3. Check status
bc cloudflared psNote: Install the bc CLI with:
curl -fsSL https://raw.githubusercontent.com/beevelop/beecompose/main/scripts/install.sh | sudo bash
# 1. Create environment file
cat > .env.cloudflared << 'EOF'
COMPOSE_PROJECT_NAME=cloudflared
CF_TUNNEL_TOKEN=eyJhIjoiYWJjZGVmMTIzNDU2Nzg5MCIsInQiOiJ5b3VyLXR1bm5lbC1pZCIsInMiOiJ5b3VyLXNlY3JldCJ9
EOF
# 2. Deploy from GHCR
docker compose -f oci://ghcr.io/beevelop/cloudflared:latest --env-file .env.cloudflared up -d --pull always
# 3. Check status
docker compose -f oci://ghcr.io/beevelop/cloudflared:latest --env-file .env.cloudflared psFor maximum security, disable direct port exposure on Traefik:
cd ../traefik
# Deploy with tunnel-only override
docker compose -f docker-compose.yml -f docker-compose.tunnel.yml up -d- Docker 25.0+ (required for OCI artifact support)
- Docker Compose v2.24+
- Cloudflare account with Zero Trust (free tier available)
- Domain with DNS managed by Cloudflare
- Traefik deployed and running
| Variable | Description | Example |
|---|---|---|
CF_TUNNEL_TOKEN |
Tunnel token from Cloudflare Dashboard | eyJhIjoiYWJj... |
| Variable | Description | Default |
|---|---|---|
COMPOSE_PROJECT_NAME |
Docker Compose project name | cloudflared |
CLOUDFLARED_VERSION |
cloudflared image version | 2025.1.0 |
Both direct internet access and tunnel access work. Good for migration or redundancy.
# Traefik: normal deployment
cd services/traefik
docker compose up -d
# Cloudflared: add tunnel access
cd ../cloudflared
docker compose up -dTraffic can reach services via:
- Direct:
https://service.example.com→ Your IP:443 → Traefik - Tunnel:
https://service.example.com→ Cloudflare → cloudflared → Traefik
All traffic forced through Cloudflare Tunnel. Maximum security.
# Cloudflared: must be running
cd services/cloudflared
docker compose up -d
# Traefik: tunnel-only mode (no public ports)
cd ../traefik
docker compose -f docker-compose.yml -f docker-compose.tunnel.yml up -dTraffic can only reach services via:
- Tunnel:
https://service.example.com→ Cloudflare → cloudflared → Traefik
Configure in Cloudflare Zero Trust Dashboard under Access → Applications.
Allow anyone to access (still protected by Cloudflare WAF/DDoS):
- Create Application → Self-hosted
- Application domain:
gitlab.example.com - Policy: Allow → Everyone
Require users to have WARP client connected:
- Create Application → Self-hosted
- Application domain:
admin.example.com - Policy: Allow → Emails ending in
@yourcompany.com - Enable: Require WARP
Require authentication via IdP:
- Create Application → Self-hosted
- Application domain:
internal.example.com - Configure IdP integration (Google, Okta, GitHub, etc.)
- Policy: Allow → Emails
user@yourcompany.com
bc cloudflared logs -f # View logs
bc cloudflared restart # Restart
bc cloudflared down # Stop
bc cloudflared update # Pull and recreate
bc cloudflared exec cloudflared cloudflared tunnel info # Check tunnel status# Define alias for convenience
alias dc="docker compose -f oci://ghcr.io/beevelop/cloudflared:latest --env-file .env.cloudflared"
# View logs
dc logs -f
# Check tunnel status
dc exec cloudflared cloudflared tunnel info
# Restart
dc restart
# Stop
dc down
# Update
dc pull && dc up -dThe container includes a built-in health check that verifies tunnel connectivity:
# Check container health
docker inspect cloudflared --format='{{.State.Health.Status}}'
# View health check logs
docker inspect cloudflared --format='{{range .State.Health.Log}}{{.Output}}{{end}}'-
Verify token is correct:
echo $CF_TUNNEL_TOKEN | base64 -d
-
Check cloudflared logs:
dc logs -f cloudflared
-
Verify tunnel status in Cloudflare Dashboard
- Ensure Traefik is running and healthy
- Verify public hostname points to
http://traefik:80 - Check that cloudflared is on
traefik_defaultnetwork:docker network inspect traefik_default
- Check Access logs in Cloudflare Dashboard
- Verify WARP client is connected (if required)
- Test with policy temporarily set to "Allow Everyone"
Check logs and ensure the tunnel token is valid:
dc logs cloudflared- No public ports exposed (in tunnel-only mode)
- All traffic encrypted between Cloudflare and your server
- DDoS attacks absorbed at Cloudflare edge
- Bot protection and WAF rules apply
- Access Policies: Define who can reach each service
- Device Posture: Require managed devices, OS versions, etc.
- Session Duration: Set appropriate session timeouts
- Audit Logs: Monitor access in Cloudflare Dashboard
- Use wildcard hostname (
*.example.com) routing to Traefik - Configure Access policies per-application in Cloudflare
- Require WARP for sensitive internal tools
- Enable audit logging for compliance
- Rotate tunnel tokens periodically
- Deploy cloudflared alongside existing Traefik
- Configure public hostnames in Cloudflare Dashboard
- Test tunnel access works for all services
- Update DNS to point to Cloudflare (orange cloud)
- Switch Traefik to tunnel-only mode:
cd services/traefik docker compose -f docker-compose.yml -f docker-compose.tunnel.yml up -d - Update firewall to block ports 80/443 from public
To restore direct access:
cd services/traefik
docker compose up -d # Without tunnel override