Last active
          October 29, 2025 22:15 
        
      - 
      
- 
        Save mattdy/d741344366a4fbb86f5034adfd1ad191 to your computer and use it in GitHub Desktop. 
    Traefik on Docker Swarm accessed via Cloudflare Tunnel
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | Please see https://mattdyson.org/blog/2024/02/using-traefik-with-cloudflare-tunnels for a detailed write-up of this configuration | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | ROOT_DOMAIN=yourdomain.com | |
| HTTP_TIMEOUT=60 | |
| POLLING_INTERVAL=10 | |
| PROPAGATION_TIMEOUT=3600 | |
| TTL=300 | |
| PROVIDERS_GOOGLE_CLIENT_ID=<GOOGLE CLIENT ID> | |
| PROVIDERS_GOOGLE_CLIENT_SECRET=<GOOGLE CLIENT SECRET> | |
| SECRET=RandomTextGoesHere | |
| WHITELIST=<YOUR GOOGLE ACCOUNT EMAIL> | |
| LOG_LEVEL=INFO | |
| ZONE_ID=<YOUR CLOUDFLARE ZONE ID> | |
| TUNNEL_TOKEN=<YOUR CLOUDFLARE TUNNEL TOKEN> | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | version: '3.7' | |
| services: | |
| whoami: | |
| image: traefik/whoami | |
| command: | |
| - --name=externalapp | |
| deploy: | |
| labels: | |
| - "traefik.enable=true" | |
| - "traefik.docker.network=traefik" | |
| - "traefik.http.routers.external.rule=Host(`external.yourdomain.com`)" | |
| - "traefik.http.routers.external.entrypoints=websecure" | |
| - "traefik.http.routers.external.tls=true" | |
| - "traefik.http.routers.external.middlewares=forward-auth" | |
| - "traefik.http.services.external.loadbalancer.server.port=80" | |
| - "traefik.constraint=proxy-public" | |
| networks: | |
| traefik: | |
| external: true | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | version: '3.7' | |
| services: | |
| whoami: | |
| image: traefik/whoami | |
| command: | |
| - --name=internalapp | |
| deploy: | |
| labels: | |
| - "traefik.enable=true" | |
| - "traefik.docker.network=traefik" | |
| - "traefik.http.routers.internal.rule=Host(`internal.yourdomain.com`)" | |
| - "traefik.http.routers.internal.entrypoints=websecure" | |
| - "traefik.http.routers.internal.tls=true" | |
| - "traefik.http.services.internal.loadbalancer.server.port=80" | |
| networks: | |
| traefik: | |
| external: true | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | version: '3.7' | |
| services: | |
| reverse-proxy: | |
| image: traefik:v2.10 | |
| command: | |
| - "--log" | |
| - "--log.level=${LOG_LEVEL:-INFO}" | |
| - "--log.format=json" | |
| - "--api.insecure=true" | |
| - "--providers.docker" | |
| - "--providers.docker.swarmMode=true" | |
| - "--providers.docker.exposedbydefault=false" | |
| - "--providers.file.directory=/config" | |
| - "--providers.file.watch=true" | |
| - "--serversTransport.insecureSkipVerify=true" # Allow self-signed certificates for target hosts - https://doc.traefik.io/traefik/routing/overview/#insecureskipverify | |
| - "--metrics" | |
| - "--metrics.prometheus.buckets=0.1,0.3,1.2,5.0" | |
| - "--entrypoints.web.address=:80" | |
| - "--entrypoints.web.http.redirections.entrypoint.to=websecure" | |
| - "--entrypoints.web.http.redirections.entrypoint.scheme=https" | |
| - "--entrypoints.websecure.address=:443" | |
| - "--entrypoints.websecure.http.tls=true" | |
| - "--entrypoints.websecure.http.tls.certresolver=letsencrypt" | |
| - "--entrypoints.webinternal.address=:82" | |
| - "--certificatesresolvers.letsencrypt.acme.email=<YOUR EMAIL>" | |
| - "--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/letsencrypt.json" | |
| - "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare" | |
| - "--certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=300" | |
| - "--certificatesresolvers.letsencrypt.acme.dnschallenge.resolvers=8.8.8.8:53" | |
| secrets: | |
| - cf_token | |
| environment: | |
| - CLOUDFLARE_DNS_API_TOKEN_FILE=/run/secrets/cf_token | |
| - CLOUDFLARE_HTTP_TIMEOUT=${HTTP_TIMEOUT} | |
| - CLOUDFLARE_POLLING_INTERVAL=${POLLING_INTERVAL} | |
| - CLOUDFLARE_PROPAGATION_TIMEOUT=${PROPAGATION_TIMEOUT} | |
| - CLOUDFLARE_TTL=${TTL} | |
| deploy: | |
| restart_policy: | |
| condition: any | |
| delay: 5s | |
| max_attempts: 3 | |
| window: 120s | |
| update_config: # Start new instance before stopping existing one | |
| delay: 10s | |
| order: start-first | |
| parallelism: 1 | |
| rollback_config: | |
| parallelism: 0 | |
| order: stop-first | |
| placement: | |
| constraints: | |
| - node.role == manager | |
| labels: | |
| - traefik.enable=true | |
| - traefik.http.routers.api.rule=Host(`traefik.${ROOT_DOMAIN}`) | |
| - traefik.http.routers.api.service=api@internal | |
| - traefik.http.routers.api.entrypoints=websecure | |
| - traefik.http.routers.api.tls=true | |
| - traefik.http.services.api.loadbalancer.server.port=8080 | |
| ports: | |
| # HTTP | |
| - target: 80 | |
| published: 80 | |
| # HTTPS | |
| - target: 443 | |
| published: 443 | |
| # Web UI (enabled by --api.insecure=true) | |
| - target: 8080 | |
| published: 8080 | |
| networks: | |
| - traefik | |
| - internal | |
| volumes: | |
| # So that Traefik can listen to the Docker events | |
| - /var/run/docker.sock:/var/run/docker.sock | |
| - acme:/etc/traefik/acme | |
| - traefik:/config | |
| - cloudflare:/cloudflare | |
| traefik-forward-auth: | |
| image: thomseddon/traefik-forward-auth:2.1.0 | |
| networks: | |
| - traefik | |
| environment: | |
| - PROVIDERS_GOOGLE_CLIENT_ID=${PROVIDERS_GOOGLE_CLIENT_ID} | |
| - PROVIDERS_GOOGLE_CLIENT_SECRET=${PROVIDERS_GOOGLE_CLIENT_SECRET} | |
| - SECRET=${SECRET} | |
| - AUTH_HOST=auth.${ROOT_DOMAIN} | |
| - COOKIE_DOMAIN=${ROOT_DOMAIN} | |
| - WHITELIST=${WHITELIST} | |
| deploy: | |
| labels: | |
| - traefik.enable=true | |
| - traefik.docker.network=traefik | |
| - traefik.http.routers.auth.rule=Host(`auth.${ROOT_DOMAIN}`) | |
| - traefik.http.routers.auth.entrypoints=websecure | |
| - traefik.http.routers.auth.tls=true | |
| - traefik.http.routers.auth.tls.domains[0].main=${ROOT_DOMAIN} | |
| - traefik.http.routers.auth.tls.domains[0].sans=*.${ROOT_DOMAIN} | |
| - traefik.http.routers.auth.tls.certresolver=letsencrypt | |
| - traefik.http.routers.auth.service=auth@docker | |
| - traefik.http.services.auth.loadbalancer.server.port=4181 | |
| - traefik.http.middlewares.forward-auth.forwardauth.address=http://traefik-forward-auth:4181 | |
| - traefik.http.middlewares.forward-auth.forwardauth.trustForwardHeader=true | |
| - traefik.http.middlewares.forward-auth.forwardauth.authResponseHeaders=X-Forwarded-User | |
| - traefik.http.routers.auth.middlewares=forward-auth | |
| - traefik.constraint=proxy-public | |
| tunnel: | |
| container_name: cloudflared-tunnel | |
| image: cloudflare/cloudflared | |
| restart: unless-stopped | |
| command: tunnel run | |
| deploy: | |
| mode: replicated | |
| replicas: 3 | |
| update_config: | |
| delay: 30s | |
| order: start-first | |
| monitor: 20s | |
| networks: | |
| - traefik | |
| environment: | |
| - TUNNEL_TOKEN=${TUNNEL_TOKEN} | |
| error-pages: | |
| image: tarampampam/error-pages:2.26.0 | |
| environment: | |
| TEMPLATE_NAME: l7-dark | |
| networks: | |
| - traefik | |
| deploy: | |
| mode: replicated | |
| replicas: 2 | |
| update_config: | |
| delay: 20s | |
| order: start-first | |
| monitor: 10s | |
| labels: | |
| - traefik.enable=true | |
| - traefik.docker.network=traefik | |
| # use as "fallback" for any non-registered services (with priority below normal) | |
| - traefik.http.routers.error-pages.rule=HostRegexp(`{host:.+}`) | |
| - traefik.http.routers.error-pages.priority=10 | |
| # should say that all of your services work on https | |
| - traefik.http.routers.error-pages.tls='true' | |
| - traefik.http.routers.error-pages.entrypoints=websecure | |
| - traefik.http.routers.error-pages.middlewares=error-pages | |
| - traefik.http.services.error-pages.loadbalancer.server.port=8080 | |
| # "errors" middleware settings | |
| - traefik.http.middlewares.error-pages.errors.status=400-599 | |
| - traefik.http.middlewares.error-pages.errors.service=error-pages | |
| - traefik.http.middlewares.error-pages.errors.query=/{status}.html | |
| cloudflare-companion: | |
| image: ghcr.io/tiredofit/docker-traefik-cloudflare-companion:latest | |
| volumes: | |
| - /var/run/docker.sock:/var/run/docker.sock | |
| deploy: | |
| placement: | |
| constraints: | |
| - node.role == manager | |
| environment: | |
| - TIMEZONE=Europe/London | |
| - LOG_TYPE=CONSOLE | |
| - LOG_LEVEL=INFO | |
| - TRAEFIK_VERSION=2 | |
| - RC_TYPE=CNAME | |
| - TARGET_DOMAIN=${ROOT_DOMAIN} | |
| - REFRESH_ENTRIES=TRUE | |
| - DOCKER_SWARM_MODE=TRUE | |
| - ENABLE_TRAEFIK_POLL=TRUE | |
| - TRAEFIK_POLL_URL=https://traefik.${ROOT_DOMAIN}/api | |
| - TRAEFIK_FILTER_LABEL=traefik.constraint | |
| - TRAEFIK_FILTER=proxy-public | |
| - DOMAIN1=${ROOT_DOMAIN} | |
| - DOMAIN1_ZONE_ID=${ZONE_ID} | |
| - DOMAIN1_PROXIED=TRUE | |
| restart: always | |
| networks: | |
| - internal | |
| secrets: | |
| - cf_token | |
| networks: | |
| traefik: | |
| external: true | |
| internal: | |
| volumes: | |
| acme: | |
| traefik: | |
| cloudflare: | |
| secrets: | |
| cf_token: | |
| external: true | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment
  
            
I think this seems to be the missing piece for me as well, I know I have to be missing a config, or setup on the cloudflare side, I have a healthy tunnel, I just have not been able to figure out how to setup the "Published application routes" section so I believe that's why the cloudflare-companion app will not start.
Published application routes - Basic Information:
cloudflare-companion log: # This just scrolls until app stopped
TEST Application.yml
Test Traefik Rule:
TEST cloudflare-companion.yml