Docker Compose: Traefik Reverse Proxy

Type: Software Reference Confidence: 0.93 Sources: 7 Verified: 2026-02-27 Freshness: 2026-02-27

TL;DR

Constraints

Quick Reference

ComponentDocker Label / ConfigPurposeExample Value
Router ruletraefik.http.routers.{name}.ruleMatch incoming requestsHost(`app.example.com`)
Router entrypointtraefik.http.routers.{name}.entrypointsBind to HTTP or HTTPS listenerwebsecure
Router TLStraefik.http.routers.{name}.tlsEnable TLS on this routertrue
Router cert resolvertraefik.http.routers.{name}.tls.certresolverUse ACME resolver for certsletsencrypt
Service porttraefik.http.services.{name}.loadbalancer.server.portBackend container port3000
Middleware reftraefik.http.routers.{name}.middlewaresAttach middlewares to routerauth@docker,ratelimit@docker
Middleware basic authtraefik.http.middlewares.{name}.basicauth.usersHTTP basic auth credentialsadmin:$$apr1$$...
Middleware redirecttraefik.http.middlewares.{name}.redirectscheme.schemeForce HTTPS redirecthttps
Middleware rate limittraefik.http.middlewares.{name}.ratelimit.averageRequests per second (average)100
Middleware rate bursttraefik.http.middlewares.{name}.ratelimit.burstMax burst above average50
Middleware headerstraefik.http.middlewares.{name}.headers.stsSecondsHSTS header duration31536000
Enable/disabletraefik.enableOpt-in/out of Traefik discoverytrue or false
Networktraefik.docker.networkSpecify which Docker networkproxy
Entrypoint (static)--entrypoints.web.addressHTTP listener address:80
Entrypoint (static)--entrypoints.websecure.addressHTTPS listener address:443

Decision Tree

START: Which reverse proxy should you use with Docker?
├── Need automatic Docker service discovery via labels?
│   ├── YES → Use Traefik (native Docker provider, zero-config routing)
│   └── NO → Consider Nginx or Caddy (manual config, higher raw throughput)
│
├── Running Kubernetes?
│   ├── YES → Use Traefik IngressRoute CRDs or Nginx Ingress Controller
│   └── NO (Docker Compose) ↓
│
├── Need automatic Let's Encrypt with wildcard certs?
│   ├── YES → Traefik with DNS challenge (supports *.domain.tld via Lego library)
│   └── NO (single-domain certs) → Traefik HTTP challenge OR Caddy (both auto-HTTPS)
│
├── Need maximum raw throughput (>50K req/s)?
│   ├── YES → Nginx (highest performance) or HAProxy
│   └── NO → Traefik (sufficient for most workloads, easier config)
│
├── Want simplest possible config with auto-HTTPS?
│   ├── YES → Caddy (3-line Caddyfile, HTTPS by default)
│   └── NO → Traefik (more features: middlewares, metrics, tracing)
│
└── DEFAULT → Traefik for Docker Compose environments (best Docker integration)

Step-by-Step Guide

1. Create the Docker network and file structure

Create a dedicated Docker network for Traefik-to-service communication and prepare the ACME certificate storage file. [src1]

# Create external network (persists across compose restarts)
docker network create proxy

# Create directory structure
mkdir -p traefik/{dynamic,certs}

# Create ACME storage file with correct permissions
touch traefik/acme.json
chmod 600 traefik/acme.json

Verify: docker network ls | grep proxy → shows the proxy network. ls -la traefik/acme.json → shows -rw------- permissions.

2. Create the Traefik static configuration

The static configuration defines entrypoints, providers, and certificate resolvers. Use a traefik.yml file for cleaner separation from Docker Compose. [src3]

# traefik/traefik.yml -- Traefik v3 static configuration
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: proxy

certificatesResolvers:
  letsencrypt:
    acme:
      email: [email protected]
      storage: /etc/traefik/acme.json
      httpChallenge:
        entryPoint: web

log:
  level: INFO

accessLog: {}

Verify: cat traefik/traefik.yml | grep -c entryPoint → should return at least 2.

3. Create the Docker Compose file for Traefik

The core Traefik service definition with secure defaults. [src4]

# docker-compose.yml
services:
  traefik:
    image: traefik:v3.6
    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/acme.json:/etc/traefik/acme.json
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)"
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "traefik.http.routers.dashboard.tls=true"
      - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.middlewares=dashboard-auth"
      - "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$apr1$$..."

networks:
  proxy:
    external: true

Verify: docker compose config --quiet → exits with 0 (valid). docker compose up -d && docker compose logs traefik → shows entrypoints started.

4. Add a backend service with Traefik labels

Expose any Docker service through Traefik using labels. [src2]

# docker-compose.app.yml
services:
  myapp:
    image: nginx:alpine
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.myapp.rule=Host(`app.example.com`)"
      - "traefik.http.routers.myapp.entrypoints=websecure"
      - "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
      - "traefik.http.services.myapp.loadbalancer.server.port=80"

networks:
  proxy:
    external: true

Verify: curl -I https://app.example.com → returns 200 OK.

5. Configure middleware chains for production

Combine multiple middlewares for rate limiting, compression, and security headers. [src4]

# Middleware labels on any service
labels:
  - "traefik.http.middlewares.rate-limit.ratelimit.average=100"
  - "traefik.http.middlewares.rate-limit.ratelimit.burst=50"
  - "traefik.http.middlewares.compress.compress=true"
  - "traefik.http.routers.myapp.middlewares=rate-limit@docker,compress@docker"

Verify: Send 200 rapid requests → should get 429 Too Many Requests after burst limit.

6. Set up wildcard certificates with DNS challenge

For wildcard certs (*.example.com), use the DNS challenge. [src3]

# In traefik.yml -- replace httpChallenge
certificatesResolvers:
  letsencrypt:
    acme:
      email: [email protected]
      storage: /etc/traefik/acme.json
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - "1.1.1.1:53"
          - "8.8.8.8:53"

Verify: docker compose logs traefik | grep -i acme → shows wildcard cert request.

Code Examples

Docker Compose: Minimal Production Setup

# Traefik v3.6 with Let's Encrypt, 1 backend service
services:
  traefik:
    image: traefik:v3.6
    restart: unless-stopped
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=proxy"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "[email protected]"
      - "--certificatesresolvers.le.acme.storage=/acme.json"
      - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./acme.json:/acme.json
    networks:
      - proxy

  app:
    image: nginx:alpine
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`app.example.com`)"
      - "traefik.http.routers.app.entrypoints=websecure"
      - "traefik.http.routers.app.tls.certresolver=le"
    networks:
      - proxy

networks:
  proxy:
    driver: bridge

Docker Compose: Multi-Service with Middleware

# 3 services: dashboard, API, frontend -- with auth + rate limiting
services:
  traefik:
    image: traefik:v3.6
    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.yml:/etc/traefik/traefik.yml:ro
      - ./acme.json:/acme.json
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.rule=Host(`dash.example.com`)"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.middlewares=auth"
      - "traefik.http.middlewares.auth.basicauth.users=admin:$$2y$$..."

  api:
    image: myapp/api:latest
    networks: [proxy]
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.api.rule=Host(`api.example.com`)"
      - "traefik.http.routers.api.entrypoints=websecure"
      - "traefik.http.routers.api.tls.certresolver=le"
      - "traefik.http.services.api.loadbalancer.server.port=8080"
      - "traefik.http.routers.api.middlewares=rate-limit"
      - "traefik.http.middlewares.rate-limit.ratelimit.average=50"
      - "traefik.http.middlewares.rate-limit.ratelimit.burst=100"

  frontend:
    image: myapp/web:latest
    networks: [proxy]
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.web.rule=Host(`www.example.com`)"
      - "traefik.http.routers.web.entrypoints=websecure"
      - "traefik.http.routers.web.tls.certresolver=le"
      - "traefik.http.services.web.loadbalancer.server.port=3000"

networks:
  proxy:
    external: true

traefik.yml: Complete Static Configuration

api:
  dashboard: true
  insecure: false

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: proxy

certificatesResolvers:
  le:
    acme:
      email: [email protected]
      storage: /acme.json
      httpChallenge:
        entryPoint: web

log:
  level: WARN

accessLog:
  format: json

metrics:
  prometheus:
    entryPoint: websecure

Anti-Patterns

Wrong: Insecure Docker socket mount

# BAD -- writable Docker socket gives full Docker API access
volumes:
  - /var/run/docker.sock:/var/run/docker.sock

Correct: Read-only Docker socket mount

# GOOD -- read-only mount limits attack surface
volumes:
  - /var/run/docker.sock:/var/run/docker.sock:ro

Wrong: Exposing all containers by default

# BAD -- every container gets a Traefik route
command:
  - "--providers.docker=true"
  # exposedByDefault defaults to true!

Correct: Opt-in per service

# GOOD -- only explicitly enabled containers get routes
command:
  - "--providers.docker=true"
  - "--providers.docker.exposedbydefault=false"
# Then per service: traefik.enable=true

Wrong: Dashboard with insecure API

# BAD -- dashboard without auth on port 8080
command:
  - "--api.insecure=true"
ports:
  - "8080:8080"

Correct: Dashboard behind auth middleware

# GOOD -- dashboard routed through HTTPS with basic auth
labels:
  - "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)"
  - "traefik.http.routers.dashboard.service=api@internal"
  - "traefik.http.routers.dashboard.middlewares=auth"
  - "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$..."

Wrong: Missing ACME storage permissions

# BAD -- default permissions (644) cause Traefik cert storage failure
touch acme.json

Correct: Restrictive ACME file permissions

# GOOD -- chmod 600 for certificate private key storage
touch acme.json
chmod 600 acme.json

Wrong: Deprecated v2 rule syntax in v3

# BAD -- v2 Headers (plural) and regex PathPrefix deprecated
labels:
  - "traefik.http.routers.app.rule=Headers(`X-Custom`, `value`)"
  - "traefik.http.routers.app.rule=PathPrefix(`/api/{id:[0-9]+}`)"

Correct: v3 rule matchers syntax

# GOOD -- v3 uses Header (singular) and PathRegexp
labels:
  - "traefik.http.routers.app.rule=Header(`X-Custom`, `value`)"
  - "traefik.http.routers.app.rule=PathRegexp(`/api/[0-9]+`)"

Common Pitfalls

Diagnostic Commands

# Check Traefik container status
docker compose ps traefik
docker compose logs --tail=50 traefik

# List all discovered routers
docker compose exec traefik wget -qO- http://localhost:8080/api/http/routers 2>/dev/null | python3 -m json.tool

# List all registered services
docker compose exec traefik wget -qO- http://localhost:8080/api/http/services 2>/dev/null | python3 -m json.tool

# Check certificate status
cat traefik/acme.json | python3 -m json.tool | grep -A2 "domain"

# Test HTTPS connectivity
curl -vI https://app.example.com 2>&1 | grep -E "subject|issuer|HTTP"

# Verify HTTP-to-HTTPS redirect
curl -sI http://app.example.com | grep -i location

# Check Docker network connectivity
docker network inspect proxy --format '{{range .Containers}}{{.Name}} {{end}}'

# Verify acme.json permissions (expect -rw-------)
ls -la traefik/acme.json

Version History & Compatibility

VersionStatusRelease DateBreaking ChangesMigration Notes
v3.6Current2025-12NoneLatest stable
v3.3Stable2025-01DNS challenge config reorganized, Swarm labels deprecatedUpdate DNS provider config
v3.0GA2024-04New rule matchers, Docker/Swarm split, Marathon removedFollow v2-to-v3 migration guide
v2.11Maintenance2024-01Last v2 release; upgrade to v3
v2.xEOL2022-2024Must migrate through v2.11
v1.xEOL2019Complete rewrite in v2

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Multiple Docker services need HTTP/HTTPS routingSingle static site with no dynamic backendsNginx or Caddy as simple web server
Need automatic Let's Encrypt per serviceHardware load balancer or CDN handles TLSCDN/LB + simple upstream proxy
Zero-downtime Docker rolling deploysNeed max throughput (>50K req/s per core)Nginx or HAProxy
Want dashboard to monitor routes and middlewareRunning KubernetesTraefik IngressRoute CRDs or Nginx Ingress
Dynamic service discovery without config changesPrefer declarative config files over labelsCaddy with Caddyfile or Nginx conf.d
Need middleware chains (auth, rate-limit, headers)Simple port forwarding onlyDocker port mapping or socat

Important Caveats

Related Units