Traefik Edge Stack
Reverse proxy for everything on this host. The goal: keep Cloudflare in front, expose a private LAN entrypoint, and let Docker stacks self-register through labels without leaking secrets.
Architecture Snapshot
-
Static config (
traefik.yml)- EntryPoints:
web/websecureon10.0.0.225,internal_web/internal_websecureon192.168.50.4. - Trusted IP lists are managed by
scripts/update_cloudflare_ips.py. - Docker provider is discovery-only; every container opts in with labels.
- File provider loads everything in
dynamic.d/.
- EntryPoints:
-
Dynamic config (
dynamic.d/)middlewares/– retry, compression, CrowdSec (rendered from template).transports/fast-upstreams.yml– shared connection pool tuning.routers/– internal-only routers (public ones stay in labels).
-
Host networking
Traefik runs withnetwork_mode: hostso it can bind to both IPs simultaneously. Switching to bridge mode would require duplicating Traefik or adding another L4 hop, so host mode stays.
Secrets Workflow
- Copy
.env.example→.envand fill:CLOUDFLARE_EMAIL,CLOUDFLARE_DNS_API_TOKENCROWDSEC_LAPI_KEY
- Render secret-aware dynamic files:
This uses
./scripts/render_dynamic.shtemplates/crowdsec.yml.tmpland writesdynamic.d/middlewares/crowdsec.yml(ignored by git).
Runbook
# start / update Traefik
docker compose up -d traefik
# refresh Cloudflare IPs and restart safely
python scripts/update_cloudflare_ips.py
# tail logs
tail -f traefik.log
tail -f access.log
Rotate CrowdSec keys? Edit .env, rerun render_dynamic.sh, then docker compose up -d traefik.
Service Labels Cheat Sheet
labels:
- traefik.enable=true
- traefik.http.routers.myapp.rule=Host(`app.example.com`)
- traefik.http.routers.myapp.entrypoints=websecure
- traefik.http.routers.myapp.tls.certresolver=letsencrypt
- traefik.http.routers.myapp.middlewares=crowdsec@file,retry-fast@file,compress-middleware@file
- traefik.http.services.myapp.loadbalancer.serversTransport=fast-upstreams@file
- traefik.http.services.myapp.loadbalancer.server.port=3000
- traefik.docker.network=traefik_default
Most stacks use the same middleware chain: CrowdSec bouncer (plugin), retry, and compression. Internal-only services skip CrowdSec by pointing at the internal_* entrypoints.
Performance Notes
- Access logs are buffered with headers trimmed to keep syscalls down.
- Compression enforces a 1 KB minimum and respects the client’s preferred encoding.
- Shared transport keeps 64 idle connections per backend with aggressive idle/response timeouts.
retry-fastretries once after 50 ms, smoothing transient Puma/Node hiccups without hammering backends.
Things to Remember
- Watchtower is still enabled for Traefik; pin the image tag when you need deterministic upgrades.
scripts/update_cloudflare_ips.pyrewrites the static trusted IP block and restarts Traefik—run it via cron.- Dashboard auth is intentionally disabled because access only happens from the LAN entrypoint. If that changes, re-enable
basicauth.
Description
Languages
Python
86.5%
Shell
13.5%