Table of Contents
Traefik is a modern reverse proxy and load balancer designed for microservices. It integrates seamlessly with Docker, automatically discovering services and configuring routes. Letβs build a production-ready Traefik setup.
Why Traefik?
| Feature | Traefik | Nginx | HAProxy |
|---|---|---|---|
| Docker Integration | Native | Manual | Manual |
| Auto-discovery | Yes | No | No |
| Auto HTTPS | Built-in | Certbot | Certbot |
| Configuration | Dynamic | Static | Static |
| Dashboard | Built-in | Third-party | Stats page |
| Hot Reload | Automatic | Manual | Manual |
Basic Setup
Docker Compose Configuration
# docker-compose.yml
version: '3.8'
services:
traefik:
image: traefik:v3.0
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro
- ./traefik/dynamic:/etc/traefik/dynamic:ro
- ./traefik/acme.json:/acme.json
networks:
- proxy
networks:
proxy:
name: proxy
external: true
Static Configuration
# traefik/traefik.yml
api:
dashboard: true
insecure: false
log:
level: INFO
format: json
accessLog:
format: json
filters:
statusCodes:
- "400-599"
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
address: ":443"
http:
tls:
certResolver: letsencrypt
certificatesResolvers:
letsencrypt:
acme:
email: admin@yourdomain.com
storage: /acme.json
httpChallenge:
entryPoint: web
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: proxy
file:
directory: /etc/traefik/dynamic
watch: true
Dynamic Configuration
# traefik/dynamic/middlewares.yml
http:
middlewares:
# Security headers
security-headers:
headers:
browserXssFilter: true
contentTypeNosniff: true
frameDeny: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 31536000
customFrameOptionsValue: "SAMEORIGIN"
# Rate limiting
rate-limit:
rateLimit:
average: 100
burst: 50
period: 1m
# Basic auth
basic-auth:
basicAuth:
users:
# Generate with: htpasswd -nb admin password
- "admin:$apr1$..."
# Compress responses
compress:
compress: {}
# Strip path prefix
strip-api:
stripPrefix:
prefixes:
- "/api"
Service Configuration with Labels
Simple Web Application
# docker-compose.yml
version: '3.8'
services:
webapp:
image: nginx:alpine
container_name: webapp
labels:
- "traefik.enable=true"
- "traefik.http.routers.webapp.rule=Host(`app.example.com`)"
- "traefik.http.routers.webapp.entrypoints=websecure"
- "traefik.http.routers.webapp.tls.certresolver=letsencrypt"
- "traefik.http.services.webapp.loadbalancer.server.port=80"
networks:
- proxy
networks:
proxy:
external: true
Multiple Domains
services:
webapp:
image: myapp:latest
labels:
- "traefik.enable=true"
# Main domain
- "traefik.http.routers.webapp.rule=Host(`example.com`) || Host(`www.example.com`)"
- "traefik.http.routers.webapp.entrypoints=websecure"
- "traefik.http.routers.webapp.tls.certresolver=letsencrypt"
# Redirect www to non-www
- "traefik.http.routers.webapp-www.rule=Host(`www.example.com`)"
- "traefik.http.routers.webapp-www.middlewares=www-redirect"
- "traefik.http.middlewares.www-redirect.redirectregex.regex=^https://www\\.(.+)"
- "traefik.http.middlewares.www-redirect.redirectregex.replacement=https://$${1}"
- "traefik.http.middlewares.www-redirect.redirectregex.permanent=true"
API with Path Routing
services:
api:
image: api:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.api.rule=Host(`example.com`) && PathPrefix(`/api`)"
- "traefik.http.routers.api.entrypoints=websecure"
- "traefik.http.routers.api.tls.certresolver=letsencrypt"
- "traefik.http.routers.api.middlewares=strip-api@file,rate-limit@file"
- "traefik.http.services.api.loadbalancer.server.port=3000"
Wildcard Subdomains
services:
multi-tenant:
image: saas-app:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.tenants.rule=HostRegexp(`{subdomain:[a-z]+}.example.com`)"
- "traefik.http.routers.tenants.entrypoints=websecure"
- "traefik.http.routers.tenants.tls.certresolver=letsencrypt"
- "traefik.http.routers.tenants.tls.domains[0].main=example.com"
- "traefik.http.routers.tenants.tls.domains[0].sans=*.example.com"
Dashboard Configuration
# traefik/dynamic/dashboard.yml
http:
routers:
dashboard:
rule: "Host(`traefik.example.com`)"
entryPoints:
- websecure
service: api@internal
middlewares:
- dashboard-auth
tls:
certResolver: letsencrypt
middlewares:
dashboard-auth:
basicAuth:
users:
- "admin:$apr1$xyz..."
Load Balancing
Multiple Instances
services:
webapp:
image: myapp:latest
deploy:
replicas: 3
labels:
- "traefik.enable=true"
- "traefik.http.routers.webapp.rule=Host(`app.example.com`)"
- "traefik.http.services.webapp.loadbalancer.server.port=3000"
# Sticky sessions
- "traefik.http.services.webapp.loadbalancer.sticky.cookie.name=server_id"
- "traefik.http.services.webapp.loadbalancer.sticky.cookie.secure=true"
# Health check
- "traefik.http.services.webapp.loadbalancer.healthcheck.path=/health"
- "traefik.http.services.webapp.loadbalancer.healthcheck.interval=10s"
- "traefik.http.services.webapp.loadbalancer.healthcheck.timeout=3s"
Weighted Load Balancing
# traefik/dynamic/weighted.yml
http:
services:
webapp-weighted:
weighted:
services:
- name: webapp-v1
weight: 80
- name: webapp-v2
weight: 20
services:
webapp-v1:
loadBalancer:
servers:
- url: "http://webapp-v1:3000"
webapp-v2:
loadBalancer:
servers:
- url: "http://webapp-v2:3000"
TCP/UDP Services
Database Proxy
services:
postgres:
image: postgres:15
labels:
- "traefik.enable=true"
- "traefik.tcp.routers.postgres.rule=HostSNI(`*`)"
- "traefik.tcp.routers.postgres.entrypoints=postgres"
- "traefik.tcp.services.postgres.loadbalancer.server.port=5432"
# Add to traefik.yml entryPoints:
# postgres:
# address: ":5432"
WebSocket Support
services:
websocket-server:
image: ws-server:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.ws.rule=Host(`ws.example.com`)"
- "traefik.http.routers.ws.entrypoints=websecure"
- "traefik.http.routers.ws.tls.certresolver=letsencrypt"
# WebSocket specific
- "traefik.http.services.ws.loadbalancer.server.port=8080"
- "traefik.http.services.ws.loadbalancer.sticky.cookie=true"
Monitoring and Metrics
Prometheus Metrics
# traefik.yml
metrics:
prometheus:
buckets:
- 0.1
- 0.3
- 1.2
- 5.0
addEntryPointsLabels: true
addServicesLabels: true
addRoutersLabels: true
entryPoint: metrics
entryPoints:
metrics:
address: ":8082"
Access Logs
# traefik.yml
accessLog:
filePath: "/var/log/traefik/access.log"
format: json
bufferingSize: 100
filters:
statusCodes:
- "200-299"
- "400-599"
retryAttempts: true
minDuration: "10ms"
fields:
defaultMode: keep
names:
ClientUsername: drop
headers:
defaultMode: keep
names:
User-Agent: keep
Authorization: drop
Production Best Practices
1. Secure Docker Socket
services:
docker-proxy:
image: tecnativa/docker-socket-proxy
container_name: docker-proxy
environment:
CONTAINERS: 1
SERVICES: 1
TASKS: 1
NETWORKS: 1
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- proxy
traefik:
depends_on:
- docker-proxy
environment:
- DOCKER_HOST=tcp://docker-proxy:2375
# Remove docker.sock volume
2. Resource Limits
services:
traefik:
deploy:
resources:
limits:
cpus: '1'
memory: 256M
reservations:
cpus: '0.25'
memory: 128M
3. Certificate Management
# Create acme.json with correct permissions
touch traefik/acme.json
chmod 600 traefik/acme.json
4. Error Pages
# traefik/dynamic/errors.yml
http:
middlewares:
error-pages:
errors:
status:
- "500-599"
service: error-pages@docker
query: "/{status}.html"
# Custom error pages service
services:
error-pages:
loadBalancer:
servers:
- url: "http://error-pages:8080"
Complete Production Example
# docker-compose.yml
version: '3.8'
services:
traefik:
image: traefik:v3.0
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
ports:
- "80:80"
- "443:443"
environment:
- TZ=UTC
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro
- ./traefik/dynamic:/etc/traefik/dynamic:ro
- ./traefik/acme.json:/acme.json
- ./traefik/logs:/var/log/traefik
networks:
- proxy
labels:
- "traefik.enable=true"
# Dashboard
- "traefik.http.routers.traefik.rule=Host(`traefik.example.com`)"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.routers.traefik.middlewares=auth@file"
deploy:
resources:
limits:
cpus: '1'
memory: 256M
networks:
proxy:
name: proxy
driver: bridge
Conclusion
Traefik simplifies reverse proxy configuration for Docker:
- Automatic discovery: Services register themselves
- Dynamic configuration: No restarts needed
- Built-in HTTPS: Letβs Encrypt integration
- Modern dashboard: Visual monitoring
- Production-ready: Load balancing, health checks, metrics
Start with a simple setup and add features as needed.
Running Traefik in production? Share your configuration tips!
In-Article Ad
Dev Mode
Mahmoud DEVO
Senior Full-Stack Developer
I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.
Enjoyed this article?
Subscribe to get more tech content delivered to your inbox.