Traefik + Let's Encrypt Reverse Proxy (Docker Compose)
A complete Traefik v3 docker-compose.yml with automatic Let's Encrypt certificates, an HTTPS redirect, and label-based routing — plus the DNS-challenge variant for wildcard certs on internal services.
yaml# docker-compose.yml — Traefik v3 edge router with automatic Let's Encrypt
# certificates via the HTTP-01 challenge. Every container that adds the
# traefik.* labels is discovered and routed automatically. Ports 80/443
# on the host must be free and reachable from the internet for HTTP-01.
services:
traefik:
image: traefik:v3.3
container_name: traefik
restart: unless-stopped
command:
# --- entrypoints ---
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
# redirect all HTTP to HTTPS
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
# --- docker provider (auto-discovery) ---
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
# --- Let's Encrypt (HTTP-01 challenge) ---
- "--certificatesresolvers.le.acme.httpchallenge=true"
- "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.le.acme.email=you@example.com" # YOUR email
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./letsencrypt:/letsencrypt"
# Example service: Traefik discovers it from the labels below.
whoami:
image: traefik/whoami
container_name: whoami
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.example.com`)" # YOUR domain
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls.certresolver=le"
What this does
Runs Traefik v3 as the single HTTPS entry point for your homelab and issues browser-trusted certificates from Let’s Encrypt automatically. Any container that carries the traefik.* labels — like the bundled whoami test service — is discovered and routed with no config-file reload. All plain HTTP is redirected to HTTPS.
Why this approach
Traefik’s Docker provider watches the Docker socket and builds its routing table from container labels, so adding a service is just adding four labels — the proxy config maintains itself. The HTTP-01 ACME challenge is the simplest way to get real certificates: Let’s Encrypt confirms you control the domain by fetching a token over port 80, which Traefik answers on the web entrypoint. exposedbydefault=false means no container is routed unless it explicitly opts in with traefik.enable=true, so nothing leaks by accident. The concepts behind all of this are in What Is Traefik? and the walkthrough is in How to Set Up Traefik with Let’s Encrypt.
Prerequisites
- Docker Engine + Compose plugin (docker-install-linux playbook)
- A domain you control with an
Arecord for each hostname (e.g.whoami.example.com) pointing at this host’s public IP - Ports 80 and 443 free on the host and reachable from the internet (HTTP-01 needs port 80 open)
Deploy
Save the script above as /opt/traefik/docker-compose.yml, set your real email and domain, then:
cd /opt/traefik
# acme.json must be private or Traefik refuses to use it
mkdir -p letsencrypt && touch letsencrypt/acme.json && chmod 600 letsencrypt/acme.json
sudo docker compose up -d
sudo docker compose logs -f traefik # watch for "certificate obtained"
Then visit https://whoami.example.com — a valid padlock, no warning. Add any other service by copying the four whoami labels and changing the router name and Host() rule.
Internal services: DNS-01 wildcard variant
HTTP-01 needs port 80 open to the internet, which you may not want for a purely internal lab. The DNS-01 challenge proves domain control through an API record instead, so it works with no inbound ports and can issue a single wildcard (*.example.com) covering every internal service. Swap the resolver command lines for your DNS provider (Cloudflare shown — see the full provider list):
# replace the httpchallenge lines with:
- "--certificatesresolvers.le.acme.dnschallenge=true"
- "--certificatesresolvers.le.acme.dnschallenge.provider=cloudflare"
- "--certificatesresolvers.le.acme.email=you@example.com"
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
# and pass the provider API token to the container:
environment:
- "CF_DNS_API_TOKEN=YOUR_SCOPED_TOKEN"
Pair this with Pi-hole local DNS records so *.example.com resolves to the Traefik host on your LAN.
Notes
chmod 600 acme.jsonis mandatory — Traefik exits if the ACME store is world-readable.- Use the staging CA while testing to avoid Let’s Encrypt rate limits: add
--certificatesresolvers.le.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory, confirm it works, then remove it and deleteacme.jsonto fetch real certs. - Mount the Docker socket read-only (
:ro). The socket is root-equivalent; for a hardened setup put a socket proxy in front of it. - Never publish your real domain or API token — keep the token in an
.envfile outside version control.