RIVER ROOKS // INFRASTRUCTURE ARCHIVE
~/notes/ > ha-traefik-docker.md

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.

[!TIP] Principle of Least Privilege

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:

docker-compose.traefik.yml YAML
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:

terminal stdout: curl -Iv Bash
$ 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.