Quick Start: Running Traefik v3 in 5 Minutes
Self-hosting half a dozen apps quickly turns into port-forwarding headaches. You end up memorizing port 8096 for Jellyfin, 8123 for Home Assistant, and click past red browser warnings for untrusted SSL certs. Traefik v3 kills that complexity. It listens to the Docker daemon, generates reverse proxy routing on the fly, and fetches free Let’s Encrypt SSL certificates automatically.
Start by creating a clean directory layout on your Docker host:
mkdir -p ~/homelab/traefik/data
cd ~/homelab/traefik
touch data/acme.json
chmod 600 data/acme.json
The acme.json file holds private keys and minted certificates. The chmod 600 step is strictly enforced. If file permissions permit read access to group or other users, Traefik aborts on startup with an error code 1.
Next, build an external bridge network so containers across different compose files can communicate without sharing host network stacks:
docker network create traefik-proxy
Now drop this production-ready docker-compose.yml into ~/homelab/traefik:
services:
traefik:
image: traefik:v3.1
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
ports:
- "80:80"
- "443:443"
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./data/acme.json:/acme.json
command:
- "--log.level=INFO"
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=traefik-proxy"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--certificatesresolvers.myresolver.acme.tlschallenge=true"
- "[email protected]"
- "--certificatesresolvers.myresolver.acme.storage=/acme.json"
networks:
- traefik-proxy
networks:
traefik-proxy:
external: true
Swap out [email protected] with your actual mailbox so Let’s Encrypt can notify you before certs expire. Fire up the daemon:
docker compose up -d
Traefik is now alive. It monitors the Docker socket, pushes HTTP traffic to HTTPS over port 443, and stands ready to provision TLS certs the second a new container spins up.
Deep Dive: Architecture and Automatic Container Discovery
Spinning up an internal app with legacy web servers usually means manual labor. You write upstream IP blocks, edit Nginx server files, run syntax checks, and reload daemons. Miss one semicolon, and the whole proxy reloads with errors.
Traefik takes a different approach. You define routing rules as metadata labels directly inside each application’s Compose file. Traefik handles the discovery, load balancing, and cert provisioning behind the scenes.
Core Components of Traefik v3
Four primary components handle traffic inside Traefik:
- Providers: Data sources that broadcast infrastructure states. The Docker provider queries the Docker socket API for lifecycle events like container starts and stops.
- EntryPoints: Network listeners bound to incoming ports. In our config,
weblistens on port 80 andwebsecurelistens on port 443. - Routers: Rule evaluation engines. They analyze Host headers, URL paths, and query strings, then route packets to designated backends.
- Services: Destination definitions containing target IP addresses and container listening ports.
How Docker Labels Drive Dynamic Routing
Whenever you execute docker compose up, Traefik catches the socket event in roughly 200 milliseconds. It parses container labels and updates its routing table in RAM. No process restarts. Zero dropped connections.
Here is an example running the lightweight whoami diagnostics container:
services:
whoami:
image: traefik/whoami
container_name: test-whoami
restart: unless-stopped
networks:
- traefik-proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.yourdomain.com`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls=true"
- "traefik.http.routers.whoami.tls.certresolver=myresolver"
- "traefik.http.services.whoami.loadbalancer.server.port=80"
networks:
traefik-proxy:
external: true
Because we set exposedbydefault=false, internal databases like PostgreSQL or Redis stay private. Only services explicitly stamped with traefik.enable=true get assigned public-facing routes.
Advanced Usage: DNS-01 Challenges and Custom Middleware Chains
HTTP-01 validation requires ports 80 and 443 open to the public internet. However, residential connections often run behind Carrier-Grade NAT (CGNAT) or restrictive ISP firewalls. The DNS-01 challenge bypasses open ports entirely by verifying domain ownership through temporary DNS TXT records.
Automated Wildcard SSL via Cloudflare DNS-01
DNS-01 lets you request valid wildcard certificates like *.lab.yourdomain.com without exposing a single port on your router. Everything stays secure on your 10.0.0.0/8 or 192.168.1.0/24 subnet.
Head to your Cloudflare dashboard, generate an API token with Zone.DNS:Edit scope, and update your Traefik service:
services:
traefik:
image: traefik:v3.1
container_name: traefik
restart: unless-stopped
environment:
- CF_DNS_API_TOKEN=your_cloudflare_secret_token_here
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./data/acme.json:/acme.json
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.dnsresolver.acme.dnschallenge=true"
- "--certificatesresolvers.dnsresolver.acme.dnschallenge.provider=cloudflare"
- "--certificatesresolvers.dnsresolver.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53"
- "[email protected]"
- "--certificatesresolvers.dnsresolver.acme.storage=/acme.json"
networks:
- traefik-proxy
Protecting Internal Services with Middleware
Middleware sits between the incoming router and the target service. It gives you fine-grained control to modify requests, strip URL paths, rate-limit brute-force attacks, or layer on HTTP Basic Authentication.
Hash an admin password using htpasswd. Be sure to escape the dollar signs with double dollar signs so Docker Compose does not parse them as shell variables:
echo $(htpasswd -nb admin your_secure_password) | sed -e s/\$/\$\$/g
Lock down Traefik’s internal web dashboard by applying authentication and recommended HSTS security headers:
labels:
- "traefik.enable=true"
# Dashboard Router
- "traefik.http.routers.dashboard.rule=Host(`traefik.yourdomain.com`)"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.tls.certresolver=dnsresolver"
# Attach Middleware
- "traefik.http.routers.dashboard.middlewares=auth-admin,sec-headers"
# Basic Auth Middleware Definition
- "traefik.http.middlewares.auth-admin.basicauth.users=admin:$$apr1$$XQZg..."
# Security Headers Middleware Definition
- "traefik.http.middlewares.sec-headers.headers.browserxssfilter=true"
- "traefik.http.middlewares.sec-headers.headers.contenttypenosniff=true"
- "traefik.http.middlewares.sec-headers.headers.framedenial=true"
- "traefik.http.middlewares.sec-headers.headers.stsseconds=31536000"
Practical Tips for Production HomeLabs
1. Guarding the Docker Socket with a Socket Proxy
Mounting /var/run/docker.sock into any internet-facing container creates an unnecessary attack vector. A vulnerability inside Traefik could let an attacker escape the container and execute root commands on the underlying host OS.
Put tecnativa/docker-socket-proxy in front of the socket instead. It operates as an access filter, dropping write actions like volume deletion while letting read-only container events pass through safely:
services:
docker-proxy:
image: tecnativa/docker-socket-proxy
container_name: docker-proxy
restart: unless-stopped
environment:
- CONTAINERS=1
- NETWORKS=1
- SERVICES=1
- TASKS=1
- EVENTS=1
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- socket-net
Set Traefik’s provider endpoint to tcp://docker-proxy:2375. That keeps your host socket completely off public-facing bridge networks.
2. Avoiding Let’s Encrypt Rate Limits
Let’s Encrypt enforces a ceiling of 5 duplicate certificates per domain every 7 rolling days. Spin up and tear down a new service repeatedly while debugging, and you will quickly lock yourself out for a full week.
Route test traffic to Let’s Encrypt’s staging ACME endpoint first:
- "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
Verify that your browser throws a certificate error from “Fake LE Intermediate X1”. That proves DNS challenges and routing function correctly. Delete the caserver flag, redeploy, and grab your production certificate.
3. Verifying Live Logs and Routing Tables
If a new service returns a 404 or fails to route, stream the Traefik daemon logs in real time:
docker logs -f traefik
Two mistakes cause 90% of all routing failures: forgetting to join the container to the external traefik-proxy network (triggering 504 Gateway Timeouts), or omitting server.port when a container exposes multiple ports such as 80 and 8080. Check those two settings first whenever a service refuses to respond.

