01Why Cloudflare Tunnels?
Cloudflare Tunnels are free for unlimited bandwidth. You only need a Cloudflare account and a domain using their DNS.
02Creating and Configuring a Tunnel
1# docker-compose.yml2services: 3 cloudflared: 4 image: cloudflare/cloudflared:latest5 container_name: cloudflared6 restart: unless-stopped7 command: tunnel run8 environment: 9 - TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}10 networks: 11 - proxy1213networks: 14 proxy: 15 external: trueNever commit your tunnel token to version control. Use environment variables or Docker secrets.
03Exposing Services Through the Tunnel
1# Full example with multiple services2services: 3 cloudflared: 4 image: cloudflare/cloudflared:latest5 restart: unless-stopped6 command: tunnel run7 environment: 8 - TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}9 networks: 10 - proxy1112 jellyfin: 13 image: jellyfin/jellyfin:latest14 volumes: 15 - ./config:/config16 - /media:/media:ro17 networks: 18 - proxy19 # No ports exposed - only accessible via tunnel!2021 vaultwarden: 22 image: vaultwarden/server:latest23 volumes: 24 - ./vaultwarden:/data25 networks: 26 - proxy2728networks: 29 proxy: 30 name: proxyServices don't need to expose ports to the host when using tunnels. This reduces your attack surface significantly.
04Config File Method (Alternative)
1# cloudflare-config.yml2tunnel: your-tunnel-id3credentials-file: /etc/cloudflared/creds.json45ingress: 6 - hostname: jellyfin.yourdomain.com7 service: http://jellyfin:80968 - hostname: nextcloud.yourdomain.com9 service: http://nextcloud:8010 originRequest: 11 noTLSVerify: true12 - hostname: ssh.yourdomain.com13 service: ssh://localhost:2214 # Catch-all rule (required)15 - service: http_status:40405Adding Authentication with Access Policies
1# Test that access is working2curl -I https://protected.yourdomain.com3# Should redirect to Cloudflare Access login45# Bypass for specific IPs (set in dashboard)6# Useful for local network accessUse Access policies to protect admin interfaces, databases, and internal tools even if they have their own authentication.
06WebSockets and Streaming Services
1# config.yml for streaming-heavy services2tunnel: your-tunnel-id3credentials-file: /etc/cloudflared/creds.json45ingress: 6 - hostname: jellyfin.yourdomain.com7 service: http://jellyfin:80968 originRequest: 9 # Disable HTTP/2 for better streaming compatibility10 http2Origin: false11 # Increase timeouts for long streams12 connectTimeout: 30s13 noHappyEyeballs: true14 - service: http_status:404Large file transfers (like Nextcloud uploads) may timeout. Increase connectTimeout and consider using direct connections for bulk transfers.
07Monitoring Tunnel Health
1services: 2 cloudflared: 3 image: cloudflare/cloudflared:latest4 restart: unless-stopped5 command: tunnel --metrics 0.0.0.0:2000 run6 environment: 7 - TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}8 networks: 9 - proxy1011 # Monitor the tunnel12 uptime-kuma: 13 image: louislam/uptime-kuma:114 volumes: 15 - ./uptime-kuma:/app/data16 networks: 17 - proxy18 # Add monitors for:19 # - cloudflared:2000/metrics (internal)20 # - Your public hostnames (external)Run two cloudflared replicas on different machines for high availability. Cloudflare load balances between them automatically.