Traefik v3.3's Docker provider fails to negotiate the API version against
Docker Engine 29 (API min 1.44) and falls back to 1.24, which the daemon
rejects ("client version 1.24 is too old"), leaving no container routes.
v3.7.x negotiates correctly through the socket-proxy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
92 lines
3.3 KiB
YAML
92 lines
3.3 KiB
YAML
# Traefik edge router. Static config is passed as flags (so .env can drive
|
|
# ACME email + dashboard host); dynamic config (middlewares, TLS options) is
|
|
# loaded from ./dynamic. Docker access goes through a least-privilege socket-proxy.
|
|
services:
|
|
socketproxy:
|
|
image: tecnativa/docker-socket-proxy:0.3.0
|
|
container_name: socketproxy
|
|
restart: unless-stopped
|
|
environment:
|
|
# Grant only what Traefik needs to discover routes; everything else denied.
|
|
CONTAINERS: 1
|
|
NETWORKS: 1
|
|
SERVICES: 1
|
|
TASKS: 1
|
|
EVENTS: 1
|
|
PING: 1
|
|
VERSION: 1
|
|
# Explicitly deny the dangerous surfaces.
|
|
POST: 0
|
|
EXEC: 0
|
|
IMAGES: 0
|
|
VOLUMES: 0
|
|
INFO: 0
|
|
AUTH: 0
|
|
SECRETS: 0
|
|
SWARM: 0
|
|
SYSTEM: 0
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
networks:
|
|
- socketproxy
|
|
|
|
traefik:
|
|
# v3.7+ required: Traefik v3.3's Docker provider fails to negotiate the API
|
|
# version against Docker Engine 29 (API min 1.44) and falls back to 1.24,
|
|
# which the daemon rejects ("client version 1.24 is too old"). v3.7.x
|
|
# negotiates correctly through the socket-proxy.
|
|
image: traefik:v3.7.10
|
|
container_name: traefik
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- socketproxy
|
|
command:
|
|
- "--global.checknewversion=false"
|
|
- "--global.sendanonymoususage=false"
|
|
- "--log.level=INFO"
|
|
- "--accesslog=true"
|
|
# Entry points + global HTTP->HTTPS redirect
|
|
- "--entrypoints.web.address=:80"
|
|
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
|
|
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
|
|
- "--entrypoints.websecure.address=:443"
|
|
# Providers: Docker (via socket-proxy) + file (dynamic dir)
|
|
- "--providers.docker=true"
|
|
- "--providers.docker.endpoint=tcp://socketproxy:2375"
|
|
- "--providers.docker.exposedbydefault=false"
|
|
- "--providers.docker.network=proxy"
|
|
- "--providers.file.directory=/etc/traefik/dynamic"
|
|
- "--providers.file.watch=true"
|
|
# Let's Encrypt (HTTP-01 challenge on the web entrypoint)
|
|
- "--certificatesresolvers.le.acme.email=${ACME_EMAIL}"
|
|
- "--certificatesresolvers.le.acme.storage=/acme/acme.json"
|
|
- "--certificatesresolvers.le.acme.httpchallenge=true"
|
|
- "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
|
|
# Dashboard/API (secured via labels below)
|
|
- "--api.dashboard=true"
|
|
# Prometheus metrics (scraped in Phase 6)
|
|
- "--metrics.prometheus=true"
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
volumes:
|
|
- /tank/platform/traefik/acme:/acme
|
|
- ./dynamic:/etc/traefik/dynamic:ro
|
|
networks:
|
|
- proxy
|
|
- socketproxy
|
|
labels:
|
|
- "traefik.enable=true"
|
|
# Dashboard router (HTTPS + basic auth)
|
|
- "traefik.http.routers.dashboard.rule=Host(`${TRAEFIK_DASHBOARD_HOST}`)"
|
|
- "traefik.http.routers.dashboard.entrypoints=websecure"
|
|
- "traefik.http.routers.dashboard.tls.certresolver=le"
|
|
- "traefik.http.routers.dashboard.service=api@internal"
|
|
- "traefik.http.routers.dashboard.middlewares=dashboard-auth@docker,security-headers@file"
|
|
- "traefik.http.middlewares.dashboard-auth.basicauth.users=${TRAEFIK_DASHBOARD_AUTH}"
|
|
|
|
networks:
|
|
proxy:
|
|
external: true
|
|
socketproxy:
|
|
internal: true
|