Setting Up Traefik as a Reverse Proxy for Docker Containers

Setting Up Traefik as a Reverse Proxy for Docker Containers

I've been running Traefik in my homelab for nearly two years now, and I honestly can't imagine managing multiple Docker services without it. If you're tired of manually mapping ports, juggling SSL certificates, and manually configuring Nginx for each new service, Traefik is a game-changer. It automatically discovers your Docker containers, provisions Let's Encrypt certificates, and handles routing—all through a single configuration file.

In this guide, I'll walk you through deploying Traefik with Docker Compose, configuring automatic SSL/TLS termination, and routing traffic to multiple backend services. By the end, you'll have a production-ready reverse proxy that scales with your homelab.

Why Traefik Over Nginx or Caddy?

Don't get me wrong—Nginx and Caddy are excellent. But Traefik was built for container orchestration from the ground up. Here's what sold me:

Caddy does auto-HTTPS beautifully, but it's less container-aware. Nginx is rock-solid but requires manual config updates and certificate renewal scripts. Traefik just works with Docker.

Prerequisites and Architecture

You'll need:

If you don't have a VPS yet, I'd recommend checking RackNerd. They offer reliable KVM instances for around $40 per year—more than adequate for running Traefik and several backend services.

Tip: If you're running this in a homelab behind a residential ISP, use Cloudflare Tunnel or Tailscale instead of exposing ports directly. Traefik works great with both—just proxy through the tunnel instead of direct port forwarding.

Docker Compose Configuration

Here's the core Traefik setup I use. This configuration includes the Traefik container itself, automatic Let's Encrypt provisioning, and a dashboard.

version: '3.8'

services:
  traefik:
    image: traefik:v3.0
    container_name: traefik
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/traefik.yml:ro
      - ./acme.json:/acme.json
      - ./config.yml:/config.yml:ro
    environment:
      - TRAEFIK_API_DASHBOARD=true
      - TRAEFIK_PROVIDERS_DOCKER=true
      - TRAEFIK_PROVIDERS_DOCKER_EXPOSEDBYDEFAULT=false
    networks:
      - traefik
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.rule=Host(\`traefik.yourdomain.com\`)"
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "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:$$2y$$05$$HASHED_PASSWORD_HERE"

  whoami:
    image: traefik/whoami:latest
    container_name: whoami
    restart: always
    networks:
      - traefik
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(\`whoami.yourdomain.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

This compose file sets up Traefik with two containers: Traefik itself (with dashboard), and a simple test service called "whoami". The key points:

Static Configuration File (traefik.yml)

Create a traefik.yml file in the same directory as your compose file:

api:
  dashboard: true
  debug: true

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: traefik
  file:
    filename: /config.yml
    watch: true

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

log:
  level: INFO
  filePath: /var/log/traefik/access.log

accessLog:
  filePath: /var/log/traefik/access.log

Here's what each section does:

Watch out: The acme.json file must be readable only by Traefik (permissions 600). If permissions are wrong, Let's Encrypt won't work. After creating it, run: chmod 600 acme.json

Generating Your BasicAuth Password

For the dashboard, I use HTTP Basic Auth. To generate a bcrypt-hashed password, run:

apt-get install apache2-utils
htpasswd -nB admin

This will prompt you for a password and output a line like admin:$2y$05$.... Replace the value in the docker-compose labels. In YAML, you need to escape the dollar signs, so $ becomes $$.

Starting Traefik

Create the necessary directories and files, then start the stack:

mkdir -p traefik-setup
cd traefik-setup
touch acme.json
chmod 600 acme.json

# Copy the compose and config files here, edit them with your domain

docker-compose up -d

# Check logs
docker-compose logs -f traefik

Within a few seconds, you should see Traefik starting and listening on ports 80 and 443. Visit https://traefik.yourdomain.com (replace with your domain) and log in with your basicauth credentials. You'll see the dashboard showing the test "whoami" service.

Adding More Services

To add any new Docker service to Traefik, just add labels. Here's an example with Nextcloud:

nextcloud:
  image: nextcloud:latest
  container_name: nextcloud
  restart: always
  networks:
    - traefik
  environment:
    - NEXTCLOUD_TRUSTED_DOMAINS=nextcloud.yourdomain.com
  volumes:
    - ./nextcloud:/var/www/html
  labels:
    - "traefik.enable=true"
    - "traefik.http.routers.nextcloud.rule=Host(\`nextcloud.yourdomain.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 compose file (in the services section), then run docker-compose up -d. Traefik automatically discovers it, provisions a certificate, and routes nextcloud.yourdomain.com to your container. No manual proxy configuration needed.

Middleware: Rate Limiting and Compression

Traefik supports middleware for advanced routing. Here's a static config (config.yml) that adds rate limiting and compression:

http:
  middlewares:
    ratelimit:
      rateLimit:
        average: 100
        period: 1m
        burst: 20
    compression:
      compress:
        minLength: 1024
    security-headers:
      headers:
        frameDeny: true
        browserXssFilter: true
        contentTypeNosniff: true
        sslRedirect: true
        sslHost: yourdomain.com
        sslForceHost: true

  routers:
    nextcloud:
      rule: Host(\`nextcloud.yourdomain.com\`)
      entrypoints:
        - websecure
      middlewares:
        - compression
        - security-headers
      tls:
        certResolver: letsencrypt

Then apply these in your service labels:

- "traefik.http.routers.nextcloud.middlewares=ratelimit,compression,security-headers"

Monitoring and Troubleshooting

Traefik logs are verbose and helpful. Check them with:

docker-compose logs -f traefik | grep -i error

Common issues:

The dashboard shows real-time metrics and any routing errors. It's invaluable for debugging.

Next Steps

Now that you have Traefik running, consider:

Traefik removes so much operational overhead once it's running. Future you will thank you for setting this up properly now.

Discussion