- 
      
- 
        Save in03/244eaa8e1e3543ec985f4f9f3a38978d to your computer and use it in GitHub Desktop. 
| Please see https://mattdyson.org/blog/2024/02/using-traefik-with-cloudflare-tunnels for a detailed write-up of this configuration | 
| 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> | 
| 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 | 
| 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 | 
| 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 | 
Hi, did you get success on this deploy? I did not.
Unfortunately, I ran into some issues.
I could not get the cloudflare companion to create CNAMEs correctly with a scoped API key. I had to create a global key and then it seemed to work. Not sure if I missed something.
Since I'm not using swarm, I had to remove all swarm related configuration from the docker compose files.
There may have been a couple of other fixes but as far as I got was custom 404 page working great, but no cnames resolving correctly.
I came for the docker labels, UI and cool middlewares. But I realised I'm in breach of service of some of Cloudflares terms. I'm proxying Immich, which handles large photos and videos. Apparently that's not supported. I'm now looking into Tailscale + Caddy.
Hi, did you get success on this deploy? I did not.