Skip to content

Instantly share code, notes, and snippets.

@mattdy
Last active April 14, 2025 13:43
Show Gist options
  • Save mattdy/d741344366a4fbb86f5034adfd1ad191 to your computer and use it in GitHub Desktop.
Save mattdy/d741344366a4fbb86f5034adfd1ad191 to your computer and use it in GitHub Desktop.
Traefik on Docker Swarm accessed via Cloudflare Tunnel
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
Copy link
Author

mattdy commented Mar 2, 2025

@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"

@1kazekage
Copy link

@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.

@mattdy
Copy link
Author

mattdy commented Mar 3, 2025

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

@kazesaurav
Copy link

kazesaurav commented Mar 5, 2025

@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

@kazesaurav
Copy link

@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)')))

@jstimmer
Copy link

jstimmer commented Mar 6, 2025

@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.)

@1kazekage
Copy link

@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
Copy link
Author

mattdy commented Mar 6, 2025

@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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment