---
# === IDENTITY ===
id: software/devops/docker-traefik/2026
canonical_question: "Docker Compose reference: Traefik Reverse Proxy"
aliases:
  - "Traefik Docker Compose setup"
  - "Traefik reverse proxy configuration"
  - "Traefik Let's Encrypt Docker"
  - "Traefik v3 Docker provider"
  - "Docker Compose Traefik HTTPS"
  - "Traefik entrypoints routers services"
  - "Traefik middleware Docker labels"
  - "Traefik automatic TLS certificates Docker"
entity_type: software_reference
domain: software > devops > Docker Traefik Reverse Proxy
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === VERIFICATION ===
last_verified: 2026-02-27
confidence: 0.93
version: 1.0
first_published: 2026-02-27

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "Traefik v3.0 GA (2024-04-29): new rule matchers syntax, Docker/Swarm split into separate providers, HTTP/3 no longer experimental"
  next_review: 2026-08-26
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "NEVER mount the Docker socket without read-only flag -- use /var/run/docker.sock:/var/run/docker.sock:ro in production"
  - "NEVER use --api.insecure=true in production -- it exposes the dashboard without authentication on port 8080"
  - "ALWAYS create acme.json with chmod 600 before starting Traefik -- wrong permissions cause certificate storage failures"
  - "NEVER expose Traefik on both HTTP and HTTPS without a redirect middleware -- users will access insecure endpoints"
  - "Traefik v3 requires Docker Engine 20.10+ and Compose V2 (docker compose, not docker-compose)"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Need a simple static file server or traditional web server, not a reverse proxy"
    use_instead: "software/devops/nginx-configuration/2026"
  - condition: "Running Kubernetes and need an ingress controller"
    use_instead: "software/devops/traefik-kubernetes-ingress/2026"
  - condition: "Need the simplest possible reverse proxy with automatic HTTPS and no Docker integration"
    use_instead: "software/devops/caddy-reverse-proxy/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: "tls_method"
    question: "How do you want to handle TLS/HTTPS certificates?"
    type: choice
    options: ["Let's Encrypt (automatic)", "Self-signed (development)", "Custom certificates (bring your own)", "No TLS (HTTP only)"]
  - key: "service_count"
    question: "How many backend services will Traefik route to?"
    type: choice
    options: ["1-3 services", "4-10 services", "10+ services (microservices)"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/devops/docker-traefik/2026"
suggested_citation: "Source: knowledgelib.io -- AI Knowledge Library (verified 2026-02-27)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "software/devops/docker-compose-reference/2026"
      label: "Docker Compose Reference"
    - id: "software/devops/letsencrypt-certificates/2026"
      label: "Let's Encrypt Certificate Management"
  solves: []
  alternative_to:
    - id: "software/devops/nginx-reverse-proxy/2026"
      label: "Nginx Reverse Proxy Setup"
    - id: "software/devops/caddy-reverse-proxy/2026"
      label: "Caddy Reverse Proxy Setup"
  often_confused_with:
    - id: "software/devops/traefik-kubernetes-ingress/2026"
      label: "Traefik Kubernetes Ingress Controller"

# === SOURCES (7 authoritative sources) ===
sources:
  - id: src1
    title: "Getting Started with Docker and Traefik"
    author: Traefik Labs
    url: https://doc.traefik.io/traefik/getting-started/quick-start/
    type: official_docs
    published: 2024-04-29
    reliability: authoritative
  - id: src2
    title: "Traefik Docker Provider Documentation"
    author: Traefik Labs
    url: https://doc.traefik.io/traefik/v3.3/providers/docker/
    type: official_docs
    published: 2025-01-15
    reliability: authoritative
  - id: src3
    title: "Traefik ACME Certificate Resolver"
    author: Traefik Labs
    url: https://doc.traefik.io/traefik/reference/install-configuration/tls/certificate-resolvers/acme/
    type: official_docs
    published: 2024-04-29
    reliability: authoritative
  - id: src4
    title: "Setup Traefik Proxy in Docker Standalone"
    author: Traefik Labs
    url: https://doc.traefik.io/traefik/setup/docker/
    type: official_docs
    published: 2025-01-15
    reliability: authoritative
  - id: src5
    title: "Traefik V2 to V3 Migration Documentation"
    author: Traefik Labs
    url: https://doc.traefik.io/traefik/migration/v2-to-v3/
    type: official_docs
    published: 2024-04-29
    reliability: authoritative
  - id: src6
    title: "HTTP routing with Traefik"
    author: Docker Inc.
    url: https://docs.docker.com/guides/traefik/
    type: official_docs
    published: 2025-03-01
    reliability: high
  - id: src7
    title: "Reverse Proxy Comparison: Traefik vs Caddy vs Nginx (Docker)"
    author: Programonaut
    url: https://www.programonaut.com/reverse-proxies-compared-traefik-vs-caddy-vs-nginx-docker/
    type: technical_blog
    published: 2024-11-15
    reliability: moderate_high
---

# Docker Compose: Traefik Reverse Proxy

## TL;DR

- **Bottom line**: Traefik is a cloud-native reverse proxy that automatically discovers Docker services via labels, handles TLS certificates via Let's Encrypt ACME, and dynamically updates routing without restarts.
- **Key tool/command**: `docker compose up -d` with `traefik:v3.6` image and Docker provider labels on each service.
- **Watch out for**: Mounting the Docker socket without `:ro` (read-only) gives Traefik full Docker API access -- a container escape risk in production.
- **Works with**: Docker Engine 20.10+, Compose V2 (`docker compose`), Traefik v3.0-v3.6. Linux, macOS, Windows (WSL2).

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- NEVER mount the Docker socket without read-only flag -- use `/var/run/docker.sock:/var/run/docker.sock:ro` in production
- NEVER use `--api.insecure=true` in production -- it exposes the dashboard without authentication on port 8080
- ALWAYS create `acme.json` with `chmod 600` before starting Traefik -- wrong permissions cause certificate storage failures
- NEVER expose Traefik on both HTTP and HTTPS without a redirect middleware -- users will access insecure endpoints
- Traefik v3 requires Docker Engine 20.10+ and Compose V2 (`docker compose`, not `docker-compose`)

## Quick Reference

| Component | Docker Label / Config | Purpose | Example Value |
|---|---|---|---|
| Router rule | `traefik.http.routers.{name}.rule` | Match incoming requests | `Host(\`app.example.com\`)` |
| Router entrypoint | `traefik.http.routers.{name}.entrypoints` | Bind to HTTP or HTTPS listener | `websecure` |
| Router TLS | `traefik.http.routers.{name}.tls` | Enable TLS on this router | `true` |
| Router cert resolver | `traefik.http.routers.{name}.tls.certresolver` | Use ACME resolver for certs | `letsencrypt` |
| Service port | `traefik.http.services.{name}.loadbalancer.server.port` | Backend container port | `3000` |
| Middleware ref | `traefik.http.routers.{name}.middlewares` | Attach middlewares to router | `auth@docker,ratelimit@docker` |
| Middleware basic auth | `traefik.http.middlewares.{name}.basicauth.users` | HTTP basic auth credentials | `admin:$$apr1$$...` |
| Middleware redirect | `traefik.http.middlewares.{name}.redirectscheme.scheme` | Force HTTPS redirect | `https` |
| Middleware rate limit | `traefik.http.middlewares.{name}.ratelimit.average` | Requests per second (average) | `100` |
| Middleware rate burst | `traefik.http.middlewares.{name}.ratelimit.burst` | Max burst above average | `50` |
| Middleware headers | `traefik.http.middlewares.{name}.headers.stsSeconds` | HSTS header duration | `31536000` |
| Enable/disable | `traefik.enable` | Opt-in/out of Traefik discovery | `true` or `false` |
| Network | `traefik.docker.network` | Specify which Docker network | `proxy` |
| Entrypoint (static) | `--entrypoints.web.address` | HTTP listener address | `:80` |
| Entrypoint (static) | `--entrypoints.websecure.address` | HTTPS 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]

```bash
# 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]

```yaml
# traefik/traefik.yml -- Traefik v3 static configuration
# Input:  Static config loaded at startup
# Output: Configured entrypoints, Docker provider, ACME resolver

api:
  dashboard: true              # Enable dashboard (secure it with middleware)
  insecure: false              # NEVER true in production

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure        # Auto-redirect HTTP -> HTTPS
          scheme: https
  websecure:
    address: ":443"
    http:
      tls:
        certResolver: letsencrypt

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false    # Require explicit traefik.enable=true per service
    network: proxy             # Use the dedicated proxy network

certificatesResolvers:
  letsencrypt:
    acme:
      email: admin@example.com       # CHANGE: your email for Let's Encrypt notifications
      storage: /etc/traefik/acme.json
      # caServer: https://acme-staging-v02.api.letsencrypt.org/directory  # Uncomment for testing
      httpChallenge:
        entryPoint: web              # Use HTTP-01 challenge on port 80

log:
  level: INFO                  # DEBUG for troubleshooting, WARN for production

accessLog: {}                  # Enable access logging (JSON format recommended)
```

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

### 3. Create the Docker Compose file for Traefik

This is the core Traefik service definition with secure defaults. [src4]

```yaml
# docker-compose.yml -- Traefik reverse proxy
# Input:  Docker Compose V2 format
# Output: Running Traefik instance with HTTPS, dashboard, and Docker provider

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   # Read-only Docker socket
      - ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro
      - ./traefik/acme.json:/etc/traefik/acme.json
      - ./traefik/dynamic:/etc/traefik/dynamic:ro
    networks:
      - proxy
    labels:
      # Enable Traefik for itself (dashboard)
      - "traefik.enable=true"
      # Dashboard router
      - "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.example.com`)"
      - "traefik.http.routers.traefik-dashboard.entrypoints=websecure"
      - "traefik.http.routers.traefik-dashboard.tls=true"
      - "traefik.http.routers.traefik-dashboard.tls.certresolver=letsencrypt"
      - "traefik.http.routers.traefik-dashboard.service=api@internal"
      # Basic auth middleware for dashboard
      - "traefik.http.routers.traefik-dashboard.middlewares=dashboard-auth"
      - "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$apr1$$xyz$$hashedpassword"
      # Security headers middleware (reusable)
      - "traefik.http.middlewares.security-headers.headers.stsSeconds=31536000"
      - "traefik.http.middlewares.security-headers.headers.stsIncludeSubdomains=true"
      - "traefik.http.middlewares.security-headers.headers.stsPreload=true"
      - "traefik.http.middlewares.security-headers.headers.forceSTSHeader=true"
      - "traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true"
      - "traefik.http.middlewares.security-headers.headers.browserXssFilter=true"
      - "traefik.http.middlewares.security-headers.headers.frameDeny=true"

networks:
  proxy:
    external: true
```

**Verify**: `docker compose config --quiet` --> exits with 0 (valid compose file). `docker compose up -d && docker compose logs traefik | head -20` --> shows Traefik starting with configured entrypoints.

### 4. Add a backend service with Traefik labels

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

```yaml
# docker-compose.app.yml -- Example backend service
services:
  myapp:
    image: nginx:alpine
    container_name: myapp
    restart: unless-stopped
    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=true"
      - "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
      - "traefik.http.services.myapp.loadbalancer.server.port=80"
      - "traefik.http.routers.myapp.middlewares=security-headers@docker"

networks:
  proxy:
    external: true
```

**Verify**: `curl -I https://app.example.com` --> returns `200 OK` with `Strict-Transport-Security` header. Check dashboard at `https://traefik.example.com` to see the route registered.

### 5. Configure middleware chains for production

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

```yaml
# Additional labels on any service for production hardening
labels:
  # Rate limiting middleware
  - "traefik.http.middlewares.rate-limit.ratelimit.average=100"
  - "traefik.http.middlewares.rate-limit.ratelimit.burst=50"
  - "traefik.http.middlewares.rate-limit.ratelimit.period=1s"
  # Compress responses
  - "traefik.http.middlewares.compress.compress=true"
  # Chain middlewares: rate-limit + compress + security-headers
  - "traefik.http.routers.myapp.middlewares=rate-limit@docker,compress@docker,security-headers@docker"
```

**Verify**: `curl -s -o /dev/null -w "%{http_code}" https://app.example.com` --> returns `200`. 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 instead of HTTP. [src3]

```yaml
# traefik/traefik.yml -- DNS challenge section (replace httpChallenge)
certificatesResolvers:
  letsencrypt:
    acme:
      email: admin@example.com
      storage: /etc/traefik/acme.json
      dnsChallenge:
        provider: cloudflare              # See Lego docs for all providers
        resolvers:
          - "1.1.1.1:53"
          - "8.8.8.8:53"
```

```yaml
# docker-compose.yml -- add environment variables for DNS provider
services:
  traefik:
    environment:
      - CF_API_EMAIL=admin@example.com
      - CF_DNS_API_TOKEN=your-cloudflare-api-token
```

**Verify**: `docker compose logs traefik | grep -i "acme"` --> shows certificate request for `*.example.com`. `echo | openssl s_client -connect app.example.com:443 2>/dev/null | openssl x509 -noout -subject` --> shows your domain.

## Code Examples

### Docker Compose: Minimal Production Setup

> Full script: [docker-compose-minimal-production-setup.yml](scripts/docker-compose-minimal-production-setup.yml) (36 lines)

```yaml
# Input:  Traefik v3.6 with Let's Encrypt, 1 backend
# Output: HTTPS reverse proxy with auto-cert renewal
services:
  traefik:
    image: traefik:v3.6
# ... (see full script)
```

### Docker Compose: Multi-Service with Middleware

> Full script: [docker-compose-multi-service-with-middleware.yml](scripts/docker-compose-multi-service-with-middleware.yml) (49 lines)

```yaml
# Input:  3 services behind Traefik with rate limiting + auth
# Output: Production-grade reverse proxy
services:
  traefik:
    image: traefik:v3.6
# ... (see full script)
```

### traefik.yml: Complete Static Configuration

> Full script: [traefik-yml-complete-static-configuration.yml](scripts/traefik-yml-complete-static-configuration.yml) (38 lines)

```yaml
# Input:  Traefik v3.6 static config file
# Output: Fully configured Traefik instance
api:
  dashboard: true
  insecure: false
# ... (see full script)
```

## Anti-Patterns

### Wrong: Insecure Docker socket mount

```yaml
# BAD -- writable Docker socket gives Traefik (and any attacker) full Docker API access
volumes:
  - /var/run/docker.sock:/var/run/docker.sock
```

### Correct: Read-only Docker socket mount

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

### Wrong: Exposing all containers by default

```yaml
# BAD -- every container on the Docker network gets a Traefik route
command:
  - "--providers.docker=true"
  # exposedByDefault defaults to true -- all containers are exposed!
```

### Correct: Opt-in per service

```yaml
# GOOD -- only containers with traefik.enable=true get routes
command:
  - "--providers.docker=true"
  - "--providers.docker.exposedbydefault=false"

# Then on each service:
labels:
  - "traefik.enable=true"
```

### Wrong: Dashboard with insecure API

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

### Correct: Dashboard behind auth middleware

```yaml
# GOOD -- dashboard routed through HTTPS with basic auth
command:
  - "--api.dashboard=true"
  - "--api.insecure=false"
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

```bash
# BAD -- Traefik cannot write certificates, fails silently
touch acme.json
# Default permissions: -rw-r--r-- (644) -- Traefik refuses to start
```

### Correct: Restrictive ACME file permissions

```bash
# GOOD -- only owner can read/write certificate data
touch acme.json
chmod 600 acme.json
# Permissions: -rw------- -- Traefik starts correctly
```

### Wrong: Using deprecated v2 rule syntax in v3

```yaml
# BAD -- v2 Headers/PathPrefix syntax deprecated in v3
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

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

## Common Pitfalls

- **Docker socket security**: Mounting `/var/run/docker.sock` without `:ro` exposes the full Docker API. An attacker who compromises Traefik can create privileged containers. Fix: Use `:ro` flag or a Docker socket proxy like `tecnativa/docker-socket-proxy`. [src4]
- **Certificate rate limits**: Let's Encrypt production has a limit of 50 certificates per registered domain per week. Fix: Use `caServer: https://acme-staging-v02.api.letsencrypt.org/directory` during development, switch to production only when ready. [src3]
- **acme.json permissions**: Traefik refuses to start or silently fails to store certs if `acme.json` is not `chmod 600`. Fix: Run `chmod 600 acme.json` before first start. On Windows/WSL, ensure the file is on a Linux filesystem. [src3]
- **Network mismatch**: Services must share a Docker network with Traefik. If a service is on the default `bridge` network but Traefik uses `proxy`, routing fails silently. Fix: Add `networks: [proxy]` to every service and set `traefik.docker.network=proxy`. [src2]
- **Port detection failure**: When a container exposes multiple ports, Traefik cannot auto-detect which one to route to. Fix: Always set `traefik.http.services.{name}.loadbalancer.server.port={port}` explicitly. [src2]
- **Dollar sign escaping in labels**: Docker Compose interprets `$` in label values. Bcrypt hashes for basicauth contain `$`. Fix: Escape every `$` as `$$` in docker-compose.yml labels. [src4]
- **HTTP-to-HTTPS redirect loop**: Configuring both entrypoint-level redirect and middleware redirect causes infinite loops. Fix: Use only ONE redirect method -- either `entryPoints.web.http.redirections` in static config OR a `redirectscheme` middleware, not both. [src1]
- **Traefik v2 labels on v3**: `Headers` (plural) was renamed to `Header` (singular) in v3 rule matchers. `PathPrefix` no longer supports regex -- use `PathRegexp` instead. Fix: Review the v2-to-v3 migration guide and update all label syntax. [src5]

## Diagnostic Commands

```bash
# Check Traefik container is running and healthy
docker compose ps traefik
docker compose logs --tail=50 traefik

# Verify entrypoints are listening
docker compose exec traefik wget -qO- http://localhost:8080/api/entrypoints 2>/dev/null | python3 -m json.tool

# 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 to a service
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}}'

# Debug Traefik with verbose logging
docker compose exec traefik traefik --log.level=DEBUG 2>&1 | head -50

# Verify acme.json permissions
ls -la traefik/acme.json
# Expected: -rw------- (600)
```

## Version History & Compatibility

| Version | Status | Release Date | Breaking Changes | Migration Notes |
|---|---|---|---|---|
| v3.6 | Current | 2025-12 | None | Latest stable |
| v3.3 | Stable | 2025-01 | DNS challenge config reorganized, Swarm labels deprecated | Update DNS provider config syntax |
| v3.0 | GA | 2024-04 | New rule matchers (Header vs Headers), Docker/Swarm split, Marathon/Rancher v1 removed | Follow v2-to-v3 migration guide |
| v2.11 | Maintenance | 2024-01 | — | Last v2 release; upgrade to v3 via v2.11 |
| v2.x | EOL | 2022-2024 | — | Must migrate through v2.11 first |
| v1.x | EOL | 2019 | — | Complete rewrite in v2; no direct upgrade path |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Running multiple Docker services that need HTTP/HTTPS routing | Single static site with no dynamic backends | Nginx or Caddy as a simple web server |
| Need automatic Let's Encrypt certificate management per service | Already have a hardware load balancer or CDN handling TLS | CDN/LB + simple upstream proxy |
| Want zero-downtime service updates with Docker rolling deploys | Need maximum raw throughput (>50K req/s per core) | Nginx or HAProxy for extreme performance |
| Need a dashboard to monitor routes, services, and middleware | Running Kubernetes (consider IngressRoute CRDs instead) | Traefik Kubernetes Ingress or Nginx Ingress |
| Want dynamic service discovery without config file changes | Prefer declarative config files over Docker labels | Caddy with Caddyfile or Nginx with conf.d |
| Need middleware chains (auth, rate-limit, headers, compress) | Simple port forwarding with no middleware needs | Docker port mapping or socat |

## Important Caveats

- Docker socket access is a security-sensitive operation -- even with `:ro`, Traefik can read all container metadata including environment variables (which may contain secrets). Consider using a Docker socket proxy for defense-in-depth.
- Let's Encrypt HTTP-01 challenge requires port 80 to be publicly accessible during certificate issuance. If your firewall blocks port 80, use TLS-ALPN-01 (port 443) or DNS-01 challenge instead.
- Traefik v3's `exposedByDefault: false` is strongly recommended -- without it, every container on the Docker network gets a public route, including databases and internal services.
- The Traefik dashboard API (`api@internal`) must NEVER be exposed without authentication. Even read-only access leaks your entire infrastructure topology.
- ACME certificate storage (`acme.json`) contains private keys. Back it up securely and never commit it to version control.
- When using Docker Compose with multiple files (`-f`), all services that need Traefik routing must be on the same external Docker network.

## Related Units
<!-- Generated from related_kos frontmatter -->

- [Docker Compose Reference](/software/devops/docker-compose-reference/2026)
- [Let's Encrypt Certificate Management](/software/devops/letsencrypt-certificates/2026)
- [Nginx Reverse Proxy Setup](/software/devops/nginx-reverse-proxy/2026)
- [Caddy Reverse Proxy Setup](/software/devops/caddy-reverse-proxy/2026)
- [Traefik Kubernetes Ingress Controller](/software/devops/traefik-kubernetes-ingress/2026)
