-
-
Save mattdy/d741344366a4fbb86f5034adfd1ad191 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> | |
TUNNEL_TOKEN=<YOUR CLOUDFLARE TUNNEL TOKEN> |
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 |
@mattdy okay I see it authenticated now but seeing below error:
Max retries exceeded with url: /api/api/http/routers (Caused by SSLError(SSLError(1, '[SSL: TLSV1_UNRECOGNIZED_NAME] tlsv1 unrecognized name (_ssl.c:1000)')))
@mattdy okay I see it authenticated now but seeing below error:
Max retries exceeded with url: /api/api/http/routers (Caused by SSLError(SSLError(1, '[SSL: TLSV1_UNRECOGNIZED_NAME] tlsv1 unrecognized name (_ssl.c:1000)')))
I had so many issues with acme that I just disabled it and only manage DNS (direct CNAME) records via cloudflare-companion and left SSL to be sorted out by Cloudflare but allowing https through the tunnel (using no TLSVerify and 443 on CF tunnel path | CF SSL/TLS: Full (strict) and edge certificates: 'always use HTTPS' & 'automatic HTTPS rewrites' set to true.)
@mattdy okay I see it authenticated now but seeing below error:
Max retries exceeded with url: /api/api/http/routers (Caused by SSLError(SSLError(1, '[SSL: TLSV1_UNRECOGNIZED_NAME] tlsv1 unrecognized name (_ssl.c:1000)')))I had so many issues with acme that I just disabled it and only manage DNS (direct CNAME) records via cloudflare-companion and left SSL to be sorted out by Cloudflare but allowing https through the tunnel (using no TLSVerify and 443 on CF tunnel path | CF SSL/TLS: Full (strict) and edge certificates: 'always use HTTPS' & 'automatic HTTPS rewrites' set to true.)
Did you do this config only on Cloudflare portal or something on docker compose as well, if you can help on that.
@mattdy okay I see it authenticated now but seeing below error:
Max retries exceeded with url: /api/api/http/routers (Caused by SSLError(SSLError(1, '[SSL: TLSV1_UNRECOGNIZED_NAME] tlsv1 unrecognized name (_ssl.c:1000)')))
Maybe try without /api
at the end of TRAEFIK_POLL_URL? Other than that, I'm afraid I don't know!
@mattdy got the SSL working, but companion is failing to authenticate with cloudflare:
2025-03-06.01:00:14 [STARTING] ** [traefik-cloudflare-companion] [6] Starting Traefik Cloudflare Companion
2025-03-06T01:00:14+0530 INFO | Found Service ID: {obfuscate} with Hostname webtop.{obfuscate}
Traceback (most recent call last):
File "/usr/sbin/cloudflare-companion", line 551, in
File "/usr/sbin/cloudflare-companion", line 405, in sync_mappings
File "/usr/sbin/cloudflare-companion", line 177, in point_domain
File "/usr/lib/python3.12/site-packages/CloudFlare/cloudflare.py", line 747, in get
CloudFlare.exceptions.CloudFlareAPIError: Unable to authenticate request
2025-03-06.01:00:16 [STARTING] ** [traefik-cloudflare-companion] [7] Starting Traefik Cloudflare Companion
2025-03-06T01:00:16+0530 INFO | Found Service ID: {obfuscate} with Hostname webtop.{obfuscate}
Traceback (most recent call last):
File "/usr/sbin/cloudflare-companion", line 551, in
File "/usr/sbin/cloudflare-companion", line 405, in sync_mappings
File "/usr/sbin/cloudflare-companion", line 177, in point_domain
File "/usr/lib/python3.12/site-packages/CloudFlare/cloudflare.py", line 747, in get
CloudFlare.exceptions.CloudFlareAPIError: Unable to authenticate request
My config for companion app:
cloudflare-companion:
image: ghcr.io/tiredofit/docker-traefik-cloudflare-companion:latest
container_name: cloudflare-companion2
volumes:
- /var/run/docker.sock:/var/run/docker.sock
# - ./cf-token:/run/secrets/cf-token:ro
environment:
- TIMEZONE=Asia/Kolkata
- LOG_TYPE=CONSOLE
- LOG_LEVEL=INFO
- TRAEFIK_VERSION=2
- CF_EMAIL={obfuscate}
- CF_TOKEN={obfuscate}
- RC_TYPE=CNAME
- CF_DNS_API_TOKEN={obfuscate}
- TARGET_DOMAIN=htb.in.net
- REFRESH_ENTRIES=TRUE
- DOCKER_SWARM_MODE=FALSE
- ENABLE_TRAEFIK_POLL=TRUE
- TRAEFIK_POLL_URL=https://traefik.${ROOT_DOMAIN}/api
- TRAEFIK_FILTER_LABEL=traefik.constraint
- TRAEFIK_FILTER=proxy-public
- DOMAIN1={obfuscate}
- DOMAIN1_ZONE_ID={obfuscate}
- DOMAIN1_PROXIED=TRUE
restart: always
networks:
- internal