-
-
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 |
@kazesaurav - Unless you've obfuscated the logs, it looks like you haven't set the ROOT_DOMAIN correctly - it's trying to connect to "traefik.domain.com"
It's obfuscated.
It's obfuscated.
In that case, it looks like there's an issue with verifying your SSL certificate for that domain, I'd check that the certificate is being provisioned properly by Traefik
@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
sync_mappings(get_initial_mappings(traefik_included_hosts, traefik_excluded_hosts), doms)
File "/usr/sbin/cloudflare-companion", line 405, in sync_mappings
if point_domain(k, domain_infos):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/sbin/cloudflare-companion", line 177, in point_domain
records = cf.zones.dns_records.get(domain_info['zone_id'], params={u'name': name})
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/site-packages/CloudFlare/cloudflare.py", line 747, in get
raise CloudFlareAPIError(e=e) from None
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
sync_mappings(get_initial_mappings(traefik_included_hosts, traefik_excluded_hosts), doms)
File "/usr/sbin/cloudflare-companion", line 405, in sync_mappings
if point_domain(k, domain_infos):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/sbin/cloudflare-companion", line 177, in point_domain
records = cf.zones.dns_records.get(domain_info['zone_id'], params={u'name': name})
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/site-packages/CloudFlare/cloudflare.py", line 747, in get
raise CloudFlareAPIError(e=e) from None
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
@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!
@kazesaurav - Unless you've obfuscated the logs, it looks like you haven't set the ROOT_DOMAIN correctly - it's trying to connect to "traefik.domain.com"