# 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: image: traefik:v3.3 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