Traefik
Traefik is the reverse proxy every other service sits behind — it terminates TLS and routes requests to the right container. Set it up first.
Verified 29 Jul 2026 against traefik:v3.6
Not exercised: recovering the proxy from a backup. The certificate store carries file permissions that a careless restore flattens, and nothing else is reachable until Traefik is back.
Installation
Section titled “Installation”This path uses Cloudflare DNS-01 for a wildcard certificate — it doesn’t require port 80 to be publicly reachable. Needs: a domain with DNS at Cloudflare, and a Cloudflare API token (My Profile → API Tokens → Custom Token, with Zone:DNS:Edit + Zone:Zone:Read).
Don’t use Cloudflare, or don’t want a wildcard? See “Certificate strategy” under Going further — per-domain certificates work with or without Cloudflare.
-
From
core/traefik/:Terminal window cp .env.example .env -
Set these in
.env:ACME_EMAIL=you@example.comTRAEFIK_DASHBOARD_HOST=traefik.example.comCF_DNS_API_TOKEN=<your Cloudflare token>ACME_WILDCARD_DOMAIN=example.com -
Render and start:
Terminal window bash ops/scripts/render.shdocker compose up -d -
Confirm both containers are healthy:
Terminal window docker compose ps
Verify
Section titled “Verify”curl -I https://<your-dashboard-domain>The dashboard is Tailscale-only by default — connect over your tailnet to reach it.
Check certificate issuance:
docker exec traefik-core cat /etc/traefik/acme/acme.json | jq '.[] | .Certificates[]?.domain // "no certs yet"'Going further
Section titled “Going further”Networking: IPv4-only or dual-stack?
Section titled “Networking: IPv4-only or dual-stack?”The installation above is IPv4-only, which works for most setups. The failure dual-stack exists to prevent, in this architecture, is Tailscale: it always hands a client both addresses, and if the client connects over IPv6 while Traefik’s network is IPv4-only, Traefik loses the real client address and may wrongly refuse a Tailscale-only service.
That is the case this blueprint has hit, not the only one IPv6 matters for — another VPN that assigns IPv6, or a service reached directly over public IPv6 rather than through a proxy, raises the same question. Traffic arriving through a Cloudflare-proxied (orange-cloud) record is unaffected, because the real address is recovered from a trusted forwarded header instead of from the connection.
No extra setup. Skip this section if you don’t use Tailscale, or haven’t run into this.
-
Generate your own IPv6 ULA prefix — never reuse a documentation example:
Terminal window RANDOM_HEX=$(openssl rand -hex 5)printf 'fd%s:%s:%s::/64\n' "${RANDOM_HEX:0:2}" "${RANDOM_HEX:2:4}" "${RANDOM_HEX:6:4}" -
Check for subnet conflicts:
Terminal window docker network lsdocker network inspect <network-name> --format '{{range .IPAM.Config}}{{.Subnet}} {{end}}' -
Back up and edit
/etc/docker/daemon.json, merging in:{"ipv6": true,"ip6tables": true,"userland-proxy": false}Then restart the daemon — this briefly affects every container on the host:
Terminal window sudo systemctl restart docker -
In
core/traefik/.env:PUBLIC_NETWORK_SUBNET_V4=172.30.0.0/16PUBLIC_NETWORK_SUBNET_V6=<your generated prefix>/64COMPOSE_FILE=docker-compose.yml:network-dual-stack.yml
Full migration guide for adding this to an already-running deployment: core/traefik/docs/ipv6-dual-stack.md.
Certificate strategy
Section titled “Certificate strategy”The path above is one of three. Two more work too — the only hard constraint is that wildcard certificates require DNS-01, since Let’s Encrypt doesn’t issue wildcards over HTTP-01 at all, regardless of this Blueprint.
One DNS-01 certificate covers *.example.com — what the installation above sets up. Needs a Cloudflare account and token; does not need port 80 reachable from the internet. Best when all your services live under one domain, and avoids registering every subdomain in public Certificate Transparency logs.
Each application requests its own DNS-01 certificate through Cloudflare — still no public port 80 needed. Leave ACME_WILDCARD_DOMAIN unset; in each app’s .env set APP_TRAEFIK_CERT_RESOLVER=cloudflare-dns, and uncomment the tls.certresolver label in its docker-compose.yml.
Each application requests its own certificate via the standard HTTP-01 challenge — no Cloudflare account or token at all. Requires port 80 to be publicly reachable on this server. Leave ACME_WILDCARD_DOMAIN unset; in each app’s .env set APP_TRAEFIK_CERT_RESOLVER=httpResolver, and uncomment the tls.certresolver label.
These combine, too: keep a wildcard for your main domain and let individual apps opt into their own certificate (either resolver) via the same per-app label.
Cloudflare’s proxy status (orange-cloud vs. DNS-only) is a separate, per-record choice — independent of certificate strategy and resolver, and irrelevant to the HTTP-01 path above.
Troubleshooting
Section titled “Troubleshooting”docker compose logs traefik 2>&1 | grep -iE "acme|cert|challenge" | tail -20Most first-setup certificate failures are a Cloudflare token missing zone permissions, or a mismatch between ACME_WILDCARD_DOMAIN and the per-app tls.certresolver label state.
Updates
Section titled “Updates”# Read release notes first: https://github.com/traefik/traefik/releases# Bump TRAEFIK_IMAGE in .env, then:bash ops/scripts/render.shdocker compose pulldocker compose up -ddocker compose logs traefik 2>&1 | grep -i error # should be emptyNever jump major versions without reading the migration guide — the configuration schema changes.
Repository files
Section titled “Repository files”core/traefik/README.md— full setup, security middleware system, CrowdSec plugin integrationcore/traefik/docs/ipv6-dual-stack.md— dual-stack networking in depthcore/traefik/.env.example— all configurable variablesdocs/standards/traefik-security.md— access policies, security levels, TLS profilesTROUBLESHOOTING.md— symptoms this blueprint has hit, with their fixesdocs/standards/troubleshooting.md— the layer-by-layer method and command reference
© 2026 SecDockBlueCode Apache-2.0Site content licence not yet decided