Last active
April 14, 2025 13:43
-
-
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
Maybe try without
/api
at the end of TRAEFIK_POLL_URL? Other than that, I'm afraid I don't know!