Building a High-Availability Traefik Router with Docker Compose
1.0 Ingress Architecture & Topologies
When running multi-tier web applications across self-hosted bare-metal clusters, an edge reverse proxy is indispensable. Managing static Nginx reverse proxy virtual host configs by hand becomes unmaintainable once container fleets scale past twenty services.
Traefik v3.1 solves this by inspecting the Docker socket daemon directly. When a new container is deployed, Traefik dynamically binds routing rules, attaches middlewares, and provisions automated wildcard TLS certificates without restarting the daemon.
+-------------------------------------------------------------------------+
| [INTERNET / WAN INGRESS] ==> Cloudflare DNS (DNS-01 ACME Challenge) |
+-------------------------------------------------------------------------+
||
\/
+-------------------------------------------------------------------------+
| gw-node01.lan (OPNsense 24.7 Gateway / NAT Port 443 Forwarding) |
+-------------------------------------------------------------------------+
||
\/ 10GbE VLAN 20 (Server DMZ)
+-------------------------------------------------------------------------+
| pve-node02.lan :: Traefik v3.1 Reverse Proxy Container (:443/:80) |
+-------------------------------------------------------------------------+
|| || ||
\/ (Docker Internal) \/ (Docker Internal) \/ (WireGuard)
+--------------------+ +--------------------+ +--------------------+
| Forgejo Git Server | | CyberChef Security | | Uptime Kuma Status |
+--------------------+ +--------------------+ +--------------------+
2.0 Automated DNS-01 ACME with Cloudflare
Standard HTTP-01 challenges require opening port 80/tcp publicly to the internet, exposing unauthenticated ingress surfaces. By employing the DNS-01 challenge, Traefik proves domain ownership by creating temporary _acme-challenge.rooks.dev TXT records through the Cloudflare API.
Do not use your Global Cloudflare API Key. Instead, generate a scoped API Token with permissions restricted strictly to: Zone.Zone:Read and Zone.DNS:Edit scoped exclusively to your primary apex zone.
3.0 Complete Docker Compose Specification
Below is the production-tested compose.yaml definition. It mounts the Docker socket in read-only mode (:ro) and stores ACME certificate state inside an encrypted acme.json file with strict 0600 permissions:
services:
traefik:
image: traefik:v3.1.2
container_name: edge-traefik-proxy
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- proxy-tier
ports:
- "80:80"
- "443:443"
environment:
- CF_DNS_API_TOKEN_FILE=/run/secrets/cf_token
secrets:
- cf_token
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./data/traefik.yml:/etc/traefik/traefik.yml:ro
- ./data/acme.json:/acme.json
labels:
- "traefik.enable=true"
- "traefik.http.routers.api.rule=Host(`proxy.rooks.dev`)"
- "traefik.http.routers.api.entrypoints=websecure"
- "traefik.http.routers.api.tls.certresolver=cloudflare"
- "traefik.http.routers.api.service=api@internal"
secrets:
cf_token:
file: ./secrets/cloudflare_api_token.txt
networks:
proxy-tier:
external: true
4.0 Configuration Parameters Matrix
Key parameters governing the Traefik core engine:
| Directive Parameter | Value | Scope | Behavior Description |
|---|---|---|---|
providers.docker.exposedbydefault |
false |
Core Security | Prevents untagged backend containers from being routed. |
entrypoints.web.http.redirections |
to: websecure |
HTTP → HTTPS | Enforces automatic HTTP 301 redirects to port 443. |
certificatesresolvers.cf.acme.dnschallenge |
cloudflare |
Let's Encrypt | Uses Cloudflare DNS provider for wildcard propagation. |
certificatesresolvers.cf.acme.storage |
/acme.json |
Persistent Storage | Encrypted JSON database for RSA/ECDSA key pairs. |
5.0 Verification & Failover Testing
Verify TLS certificate issuance and HTTP/3 ALPN negotiation directly via curl:
$ curl -Iv https://proxy.rooks.dev
* Server certificate:
* subject: CN=*.rooks.dev
* start date: Jul 15 00:00:00 2026 GMT
* expire date: Oct 13 23:59:59 2026 GMT
* issuer: C=US, O=Let's Encrypt, CN=R3
* SSL certificate verify ok.
< HTTP/2 200 OK
< server: traefik
< strict-transport-security: max-age=63072000; includeSubDomains; preload
Zero downtime updates are achieved through rolling container health checks configured inside Traefik dynamic labels.