platform/platform-infra/stacks/traefik/docker-compose.yml
Bart Van Geyt 92ee8ac05d fix(stacks/traefik): pin v3.7.10 so Docker provider works on Engine 29
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>
2026-08-09 03:52:45 +02:00

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