Using Traefik as a Reverse Proxy: Dynamic Routing for Docker Containers
We earn commissions when you shop through the links on this page, at no additional cost to you. Learn more.
When I first started self-hosting multiple services on Docker, I spent weeks manually configuring Nginx entries, restarting the container every time I added a new app, and wrestling with SSL certificate renewals. Then I discovered Traefik, and it changed everything. Unlike static reverse proxies, Traefik watches your Docker daemon in real-time, automatically discovers containers, and routes traffic to them based on labels you define—all with zero downtime. If you're running 3 or more services in Docker, Traefik will save you hours of repetitive configuration.
Why Traefik Over Nginx or Caddy?
I respect both Nginx and Caddy—they're rock-solid. But Traefik's killer feature is its tight integration with Docker. Every container label becomes routing logic. You don't edit config files and reload; you spin up a container with the right labels, and Traefik automatically wires it in. The middleware system is also cleaner than Nginx's location blocks—think built-in auth, rate limiting, and header manipulation without wrestling with regex.
Caddy is simpler if you're running bare metal, but in a containerized environment, Traefik's service discovery saves you weeks of operational burden. I prefer Traefik because:
- Zero-touch discovery: Add a label to a container, it's routed. No config files to edit.
- Built-in Let's Encrypt: Automatic SSL renewal, per-domain or wildcard.
- Dashboard: Visual overview of all routes, services, and middleware in real-time.
- Middleware pipeline: Authentication, rate limiting, security headers, path rewriting—all composable.
- Multiple entry points: Web (80), websecure (443), separate ports for APIs—all in one proxy.
Architecture Overview
Before we code, let me explain the mental model. Traefik sits in front of all your containers, listening on ports 80 and 443. It queries the Docker socket to discover running containers. When you label a container with traefik.enable=true and traefik.http.routers.myapp.rule=Host(`myapp.example.com`), Traefik creates a router pointing to that container's port. If the container is removed, the route vanishes automatically.
Traefik also manages SSL. It watches your routers and automatically requests certificates from Let's Encrypt, stores them, and renews them 30 days before expiry—all in the background.
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory" in traefik.yml to avoid quota issues.Setting Up Traefik with Docker Compose
I'll walk you through a production-ready setup. You'll need a VPS (around $40/year from RackNerd gets you a decent box with 1-2 vCPU and enough bandwidth for a homelab), a domain name, and basic Docker knowledge.
First, create a directory structure:
mkdir -p ~/traefik/{config,data}
cd ~/traefik
touch docker-compose.yml traefik.yml
Now the main Docker Compose file. This spins up Traefik, mounts the socket (so it can talk to Docker), and sets up Let's Encrypt storage:
version: '3.8'
services:
traefik:
image: traefik:v3.0
container_name: traefik
restart: always
security_opt:
- no-new-privileges:true
networks:
- traefik
ports:
- "80:80"
- "443:443"
- "8080:8080"
environment:
- TRAEFIK_API_DASHBOARD=true
- TRAEFIK_PROVIDERS_DOCKER=true
- TRAEFIK_PROVIDERS_DOCKER_EXPOSEDBYDEFAULT=false
- TRAEFIK_ENTRYPOINTS_WEB_ADDRESS=:80
- TRAEFIK_ENTRYPOINTS_WEBSECURE_ADDRESS=:443
- TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_HTTPCHALLENGE_ENTRYPOINT=web
- TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL=your-email@example.com
- TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_STORAGE=/letsencrypt/acme.json
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./data/letsencrypt:/letsencrypt
- ./traefik.yml:/traefik.yml:ro
labels:
- traefik.enable=true
- traefik.http.routers.traefik.rule=Host(`traefik.example.com`)
- traefik.http.routers.traefik.service=api@internal
- traefik.http.routers.traefik.entrypoints=websecure
- traefik.http.routers.traefik.tls.certresolver=letsencrypt
- traefik.http.middlewares.traefik-auth.basicauth.users=admin:$$2y$$05$$BCRYPTHASHEDPASSWORD
- traefik.http.routers.traefik.middlewares=traefik-auth
whoami:
image: traefik/whoami:latest
container_name: whoami
restart: always
networks:
- traefik
labels:
- traefik.enable=true
- traefik.http.routers.whoami.rule=Host(`whoami.example.com`)
- traefik.http.routers.whoami.entrypoints=websecure
- traefik.http.routers.whoami.tls.certresolver=letsencrypt
- traefik.http.services.whoami.loadbalancer.server.port=80
networks:
traefik:
driver: bridge
Replace [email protected] and example.com with your actual email and domain. The whoami service is a test app that returns the request details—useful for debugging.
For the basic auth password, generate a bcrypt hash:
htpasswd -nB admin | sed 's/\$/\$\$/g'
Copy the output and paste it into the label (the double $$ is YAML escaping for single $).
Next, create traefik.yml with advanced settings:
api:
dashboard: true
insecure: false
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
http:
tls:
certResolver: letsencrypt
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: traefik
certificatesResolvers:
letsencrypt:
acme:
email: [email protected]
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web
This config redirects all HTTP traffic to HTTPS automatically. The exposedByDefault: false means containers are invisible to Traefik until you explicitly label them.
Start Traefik:
docker-compose up -d
Check the logs:
docker-compose logs -f traefik
You should see Traefik starting, discovering the whoami container, and requesting certificates. Access the dashboard at https://traefik.example.com (using the basic auth credentials you set).
Adding Your Own Services
Once Traefik is running, adding services is dead simple. Here's a Nextcloud example:
nextcloud:
image: nextcloud:latest
container_name: nextcloud
restart: always
networks:
- traefik
environment:
- NEXTCLOUD_ADMIN_USER=admin
- NEXTCLOUD_ADMIN_PASSWORD=secure-password-here
volumes:
- ./data/nextcloud:/var/www/html
labels:
- traefik.enable=true
- traefik.http.routers.nextcloud.rule=Host(`cloud.example.com`)
- traefik.http.routers.nextcloud.entrypoints=websecure
- traefik.http.routers.nextcloud.tls.certresolver=letsencrypt
- traefik.http.services.nextcloud.loadbalancer.server.port=80
Add this to your docker-compose.yml under services, then run:
docker-compose up -d nextcloud
Traefik will detect it immediately, request a cert for cloud.example.com, and route traffic. No manual Nginx config, no service restart. Just add the service and labels.
Middleware: Authentication, Rate Limits, Headers
Traefik's middleware lets you modify requests and responses without touching application code. For example, adding HTTP Basic Auth to a service:
labels:
- traefik.enable=true
- traefik.http.routers.myapp.rule=Host(`myapp.example.com`)
- traefik.http.routers.myapp.entrypoints=websecure
- traefik.http.routers.myapp.tls.certresolver=letsencrypt
- traefik.http.middlewares.myapp-auth.basicauth.users=user:$$2y$$05$$HASHHERE
- traefik.http.routers.myapp.middlewares=myapp-auth
- traefik.http.services.myapp.loadbalancer.server.port=3000
Or rate limiting (max 10 requests per 60 seconds):
- traefik.http.middlewares.myapp-ratelimit.ratelimit.average=10
- traefik.http.middlewares.myapp-ratelimit.ratelimit.period=60s
- traefik.http.routers.myapp.middlewares=myapp-ratelimit
You can chain multiple middleware with comma-separated values:
- traefik.http.routers.myapp.middlewares=myapp-auth,myapp-ratelimit,my-headers
Monitoring and Debugging
The dashboard is your best friend. Visit https://traefik.example.com to see:
- HTTP Routers: All active routes and their matching rules.
- Services: Containers and their health status.
- Middleware: Applied middleware and config.
- Certificates: Certificate status, expiry dates, and renewal progress.
For deeper troubleshooting, check logs:
docker-compose logs traefik | grep -i error
To verify routing, use curl:
curl -I https://myapp.example.com
Or inspect Traefik's entrypoints and service discovery:
docker exec traefik traefik --help | grep -A 20 providers
Production Hardening
Before exposing Traefik to the internet, harden it:
- Secure the dashboard: Always use basic auth or middleware authentication (as shown above).
- Disable insecure mode: Set
api.insecure=falseintraefik.ymlto prevent unauthenticated API access on port 8080. - Use strong passwords: Generate bcrypt hashes for basic auth; don't reuse weak credentials.
- Monitor