Traefik, and four starter bugs
The infra repo’s starter stack provides Traefik, a docker-socket-proxy,
Portainer and a whoami placeholder. Every one of the bugs below passed
docker compose config — they only appeared when the stack ran, or when the
config was loaded into the real Traefik version.
Why a socket proxy
Section titled “Why a socket proxy”Traefik reads container labels from the Docker API. Mounting
/var/run/docker.sock — even :ro — gives it full control of the daemon:
:ro only stops the socket file being replaced, not API writes. Traefik’s own
docs warn this can hand an attacker the host. tecnativa/docker-socket-proxy
exposes only read-only listing (CONTAINERS=1, NETWORKS=1, POST=0), and
Traefik talks to tcp://docker-socket-proxy:2375.
Validate the dynamic config before deploying it
Section titled “Validate the dynamic config before deploying it”docker run --rm -d --name tf-test -v "$PWD/etc/traefik:/dyn:ro" traefik:v3.7.17 \ --providers.file.filename=/dyn/dynamic.yml --api.insecure=truedocker logs tf-test | grep ERRdocker exec tf-test wget -qO- http://127.0.0.1:8080/api/http/middlewaresdocker rm -f tf-testBug 1 — the whole middleware file was rejected
Section titled “Bug 1 — the whole middleware file was rejected”ERR Error while building configuration … error="field not found, node: passiveHealthCheck"passiveHealthCheck was inside the rate-limit middleware. In Traefik it is a
service load-balancer option. One bad field drops the entire file, so
rate-limit@file and secure-headers@file never existed — and every router
that referenced them failed. Set it per service if you want it:
traefik.http.services.<svc>.loadbalancer.passiveHealthCheck.failureWindow=30s.
Bug 2 — rate limits shared by everyone
Section titled “Bug 2 — rate limits shared by everyone”Behind Cloudflare the connecting IP is a Cloudflare edge, so per-IP limits are shared by every visitor on that edge. Group by the real visitor instead:
rate-limit: rateLimit: average: 100 burst: 200 period: 1s sourceCriterion: requestHeaderName: CF-Connecting-IPBug 3 — secure-headers told Google not to index the site
Section titled “Bug 3 — secure-headers told Google not to index the site”The shared secure-headers middleware also sent
X-Robots-Tag: "noindex, nofollow". Every public site used it. That header
moved into its own noindex@file middleware, for admin tools only.
Bug 4 — three failures from one up
Section titled “Bug 4 — three failures from one up”| Symptom | Cause | Fix |
|---|---|---|
socket-proxy crash-loops: unknown optional log level 'warn' |
Its HAProxy 3.4 (image v0.5.0) dropped warn |
LOG_LEVEL=warning |
field not found, node: strategy; routers never load, no certificate requested |
loadbalancer.server.strategy isn’t a field |
traefik.http.services.<svc>.loadbalancer.strategy=leasttime |
Traefik “unhealthy”: please enable ping |
--ping never set, and traefik healthcheck reads its own flags |
--ping=true and test: ["CMD", "traefik", "healthcheck", "--ping"] |
Start only what you mean to run
Section titled “Start only what you mean to run”make up in the starter also pulled in the monitoring and backup files, and the
core file includes Portainer — whose first visitor creates the admin
account. Start services by name instead:
docker network create traefik_publicdocker compose -f compose.yml up -d docker-socket-proxy traefik whoamiPin image versions — after checking them
Section titled “Pin image versions — after checking them”Every image the starter pinned was out of date, and two pins were wrong outright:
henrygd/beszel-hubdoes not exist on Docker Hub; the hub image ishenrygd/beszel.portainer/agent’s newest tag is a new major (3.x); keep it on the same 2.x line as the Portainer server.
Check the newest stable tag, then that it exists for your CPU:
docker manifest inspect traefik:v3.7.17 | grep -q amd64 && echo ok(Docker Hub rate-limits anonymous pulls and manifest lookups; space them out or log in.)