Cloud-Native Edge Router: Traefik v3 Dynamic Discovery, Socket Proxy & Middleware Chains
Executive Summary: Maintaining static reverse-proxy configuration files across ephemeral container environments introduces manual drift and reload downtime. This architectural guide details the production deployment of Traefik Proxy v3, utilizing dynamic label-driven service discovery via a read-only Docker Socket Proxy, composable Middleware chains (rate limiting, ForwardAuth, circuit breakers, security headers), TCP/UDP SNI routing (
HostSNI), and automated ACME DNS-01 certificate lifecycle management.
-
Zero-Reload Dynamic Discovery
Discover containers, health checks, and routing rules in real time via Docker/Podman labels without reloading processes or dropping active sockets.
-
Docker Socket Isolation
Eliminate root-equivalent
/var/run/docker.sockexposure by interposing a read-onlytecnativa/docker-socket-proxyrestricted toCONTAINERS=1. -
Composable Middleware Pipelines
Chain token-bucket rate limiters, HSTS headers, IP allowlists, and OIDC/Authelia
ForwardAuthmiddlewares across dozens of routers via single label references. -
Unified HTTP/3, TCP & UDP SNI
Route HTTP/3 (
QUIC), raw TLS-passthrough TCP streams (PostgreSQL, Redis, SMTPS), and UDP traffic through unified EntryPoints.
1. Architecture: EntryPoints, Routers, Middlewares & Services
Traefik v3 separates static startup infrastructure (EntryPoints and Providers) from dynamic runtime routing (Routers, Middlewares, and Services):
%%{init: {'flowchart': {'wrappingWidth': 600, 'subGraphTitleMargin': {'top': 15, 'bottom': 15}}}}%%
flowchart TB
subgraph EdgeEntry["EntryPoints (Ports 80 / 443)"]
Web["web (:80)<br/>Permanent 308 -> websecure"]
WebSecure["websecure (:443 TCP/UDP)<br/>TLS 1.3 + HTTP/3 + ACME"]
end
subgraph TraefikPlane["Traefik v3 Dynamic Pipeline"]
Router["Router Rule Match<br/>Host(`app.nejat-hakan.de`)"]
MWChain["Middleware Chain<br/>RateLimit -> Auth -> Headers"]
Service["LoadBalancer Service<br/>HealthCheck + Sticky Cookie"]
end
subgraph ControlDiscovery["Isolated Control Plane"]
SockProxy["Docker Socket Proxy<br/>GET /containers/json ONLY"]
end
subgraph BackendTier["Container Backends"]
App1["App Replica 1<br/>172.29.0.12:8080"]
App2["App Replica 2<br/>172.29.0.13:8080"]
end
Web -.->|"308 Redirect"| WebSecure
WebSecure --> Router
SockProxy -.->|"Watch Events"| Router
Router --> MWChain
MWChain --> Service
Service --> App1
Service --> App2
Why a Docker Socket Proxy is Mandatory
Mounting /var/run/docker.sock directly into a public-facing reverse proxy container is a critical security anti-pattern: if an attacker exploits a vulnerability in the proxy process, they can send a POST /containers/create request to the Docker API with --privileged -v /:/host, achieving instant root compromise of the physical host. By placing docker-socket-proxy on an internal socket_net network and permitting only GET requests to /containers/* and /networks/*, Traefik cannot spawn, modify, or stop containers even if compromised.
2. Production Stack: Traefik v3 + Hardened Socket Proxy
Create two distinct networks: socket_net (internal: true for isolated Docker API reads) and traefik_proxy (for routing to backend containers).
services:
socket-proxy:
image: lscr.io/linuxserver/socket-proxy:latest
container_name: traefik-socket-proxy
restart: unless-stopped
read_only: true
tmpfs:
- /run
environment:
CONTAINERS: 1
NETWORKS: 1
SERVICES: 1
TASKS: 1
POST: 0
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- socket_net
traefik:
image: traefik:v3.2
container_name: traefik-edge
restart: unless-stopped
depends_on:
- socket-proxy
security_opt:
- no-new-privileges:true
ports:
- "80:80/tcp"
- "443:443/tcp"
- "443:443/udp"
environment:
TZ: "Europe/Berlin"
CF_DNS_API_TOKEN_FILE: "/run/secrets/cf_dns_token"
volumes:
- ./traefik.yaml:/etc/traefik/traefik.yaml:ro
- ./dynamic:/etc/traefik/dynamic:ro
- ./acme:/etc/traefik/acme:Z
networks:
- socket_net
- traefik_proxy
networks:
socket_net:
internal: true
traefik_proxy:
external: true
3. Static & Dynamic Configuration Files
global:
checkNewVersion: false
sendAnonymousUsage: false
api:
dashboard: true
insecure: false
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
address: ":443"
http3: {}
http:
tls:
certResolver: letsencrypt
domains:
- main: "nejat-hakan.de"
sans:
- "*.nejat-hakan.de"
transport:
respondingTimeouts:
readTimeout: 60s
writeTimeout: 60s
idleTimeout: 180s
providers:
docker:
endpoint: "tcp://socket-proxy:2375"
exposedByDefault: false
network: traefik_proxy
file:
directory: "/etc/traefik/dynamic"
watch: true
certificatesResolvers:
letsencrypt:
acme:
email: "hostmaster@nejat-hakan.de"
storage: "/etc/traefik/acme/acme.json"
keyType: "EC384"
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
- "9.9.9.9:53"
log:
level: INFO
accessLog:
format: json
fields:
headers:
defaultMode: drop
names:
User-Agent: keep
X-Forwarded-For: keep
tls:
options:
default:
minVersion: VersionTLS12
sniStrict: true
curvePreferences:
- X25519
- CurveP384
cipherSuites:
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
http:
middlewares:
sec-headers:
headers:
stsSeconds: 63072000
stsIncludeSubdomains: true
stsPreload: true
forceSTSHeader: true
frameDeny: true
contentTypeNosniff: true
browserXssFilter: true
referrerPolicy: "strict-origin-when-cross-origin"
customResponseHeaders:
Server: ""
rate-limit-std:
rateLimit:
average: 100
burst: 50
period: 1s
lan-whitelist:
ipAllowList:
sourceRange:
- "192.168.10.0/24"
- "10.88.0.0/24"
# Master Composite Chain for Public Services
public-chain:
chain:
middlewares:
- rate-limit-std
- sec-headers
# Master Composite Chain for Internal Admin Services
internal-chain:
chain:
middlewares:
- lan-whitelist
- sec-headers
4. Attaching Backend Containers via Traefik v3 Labels
With exposedByDefault: false and reusable file-provider middleware chains (public-chain@file, internal-chain@file), onboarding any new container requires only 4 labels:
services:
whoami:
image: traefik/whoami:latest
container_name: whoami-test
networks:
- traefik_proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.nejat-hakan.de`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.middlewares=internal-chain@file"
- "traefik.http.services.whoami.loadbalancer.server.port=80"
TCP SNI Passthrough Routing (PostgreSQL / SMTPS / TLS Backends)
Traefik v3 can route encrypted TCP streams without terminating TLS (passthrough: true) or terminate TLS at the edge and forward plain TCP:
tcp:
routers:
postgres-tls:
entryPoints:
- websecure
rule: "HostSNI(`db.lan.nejat-hakan.de`)"
service: postgres-svc
tls:
passthrough: false
services:
postgres-svc:
loadBalancer:
servers:
- address: "192.168.10.50:5432"
5. Verification, ACME Storage Permissions & Diagnostics
Ensure /opt/traefik/acme/acme.json has strict 0600 permissions before startup—Traefik refuses to initialize the ACME resolver if the certificate store is group- or world-readable:
# 1. Enforce strict 0600 permissions on ACME certificate store
touch /opt/traefik/acme/acme.json
chmod 0600 /opt/traefik/acme/acme.json
# 2. Verify HTTP/3 (Alt-Svc h3=":443") and HSTS headers via curl
curl -ILs https://whoami.nejat-hakan.de | grep -iE "strict-transport|alt-svc|http/"
# 3. Inspect structured JSON access logs for latency > 100ms using jq
docker logs traefik-edge 2>&1 | jq 'select(.Duration > 100000000)'