Traefik Reverse Proxy: Complete Docker Integration Guide

Set up Traefik as a reverse proxy for Docker containers. Automatic HTTPS, load balancing, and dynamic configuration.

Mahmoud DEVO
Mahmoud DEVO
November 28, 2024 16 min read
Traefik Reverse Proxy: Complete Docker Integration Guide

Traefik is a modern reverse proxy and load balancer designed for microservices. It integrates seamlessly with Docker, automatically discovering services and configuring routes. Let’s build a production-ready Traefik setup.

Why Traefik?

FeatureTraefikNginxHAProxy
Docker IntegrationNativeManualManual
Auto-discoveryYesNoNo
Auto HTTPSBuilt-inCertbotCertbot
ConfigurationDynamicStaticStatic
DashboardBuilt-inThird-partyStats page
Hot ReloadAutomaticManualManual

Basic Setup

Docker Compose Configuration

# docker-compose.yml
version: '3.8'

services:
  traefik:
    image: traefik:v3.0
    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/dynamic:/etc/traefik/dynamic:ro
      - ./traefik/acme.json:/acme.json
    networks:
      - proxy

networks:
  proxy:
    name: proxy
    external: true

Static Configuration

# traefik/traefik.yml
api:
  dashboard: true
  insecure: false

log:
  level: INFO
  format: json

accessLog:
  format: json
  filters:
    statusCodes:
      - "400-599"

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

  websecure:
    address: ":443"
    http:
      tls:
        certResolver: letsencrypt

certificatesResolvers:
  letsencrypt:
    acme:
      email: admin@yourdomain.com
      storage: /acme.json
      httpChallenge:
        entryPoint: web

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: proxy
  file:
    directory: /etc/traefik/dynamic
    watch: true

Dynamic Configuration

# traefik/dynamic/middlewares.yml
http:
  middlewares:
    # Security headers
    security-headers:
      headers:
        browserXssFilter: true
        contentTypeNosniff: true
        frameDeny: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 31536000
        customFrameOptionsValue: "SAMEORIGIN"

    # Rate limiting
    rate-limit:
      rateLimit:
        average: 100
        burst: 50
        period: 1m

    # Basic auth
    basic-auth:
      basicAuth:
        users:
          # Generate with: htpasswd -nb admin password
          - "admin:$apr1$..."

    # Compress responses
    compress:
      compress: {}

    # Strip path prefix
    strip-api:
      stripPrefix:
        prefixes:
          - "/api"

Service Configuration with Labels

Simple Web Application

# docker-compose.yml
version: '3.8'

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

networks:
  proxy:
    external: true

Multiple Domains

services:
  webapp:
    image: myapp:latest
    labels:
      - "traefik.enable=true"
      # Main domain
      - "traefik.http.routers.webapp.rule=Host(`example.com`) || Host(`www.example.com`)"
      - "traefik.http.routers.webapp.entrypoints=websecure"
      - "traefik.http.routers.webapp.tls.certresolver=letsencrypt"
      # Redirect www to non-www
      - "traefik.http.routers.webapp-www.rule=Host(`www.example.com`)"
      - "traefik.http.routers.webapp-www.middlewares=www-redirect"
      - "traefik.http.middlewares.www-redirect.redirectregex.regex=^https://www\\.(.+)"
      - "traefik.http.middlewares.www-redirect.redirectregex.replacement=https://$${1}"
      - "traefik.http.middlewares.www-redirect.redirectregex.permanent=true"

API with Path Routing

services:
  api:
    image: api:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.api.rule=Host(`example.com`) && PathPrefix(`/api`)"
      - "traefik.http.routers.api.entrypoints=websecure"
      - "traefik.http.routers.api.tls.certresolver=letsencrypt"
      - "traefik.http.routers.api.middlewares=strip-api@file,rate-limit@file"
      - "traefik.http.services.api.loadbalancer.server.port=3000"

Wildcard Subdomains

services:
  multi-tenant:
    image: saas-app:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.tenants.rule=HostRegexp(`{subdomain:[a-z]+}.example.com`)"
      - "traefik.http.routers.tenants.entrypoints=websecure"
      - "traefik.http.routers.tenants.tls.certresolver=letsencrypt"
      - "traefik.http.routers.tenants.tls.domains[0].main=example.com"
      - "traefik.http.routers.tenants.tls.domains[0].sans=*.example.com"

Dashboard Configuration

# traefik/dynamic/dashboard.yml
http:
  routers:
    dashboard:
      rule: "Host(`traefik.example.com`)"
      entryPoints:
        - websecure
      service: api@internal
      middlewares:
        - dashboard-auth
      tls:
        certResolver: letsencrypt

  middlewares:
    dashboard-auth:
      basicAuth:
        users:
          - "admin:$apr1$xyz..."

Load Balancing

Multiple Instances

services:
  webapp:
    image: myapp:latest
    deploy:
      replicas: 3
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.webapp.rule=Host(`app.example.com`)"
      - "traefik.http.services.webapp.loadbalancer.server.port=3000"
      # Sticky sessions
      - "traefik.http.services.webapp.loadbalancer.sticky.cookie.name=server_id"
      - "traefik.http.services.webapp.loadbalancer.sticky.cookie.secure=true"
      # Health check
      - "traefik.http.services.webapp.loadbalancer.healthcheck.path=/health"
      - "traefik.http.services.webapp.loadbalancer.healthcheck.interval=10s"
      - "traefik.http.services.webapp.loadbalancer.healthcheck.timeout=3s"

Weighted Load Balancing

# traefik/dynamic/weighted.yml
http:
  services:
    webapp-weighted:
      weighted:
        services:
          - name: webapp-v1
            weight: 80
          - name: webapp-v2
            weight: 20

  services:
    webapp-v1:
      loadBalancer:
        servers:
          - url: "http://webapp-v1:3000"
    webapp-v2:
      loadBalancer:
        servers:
          - url: "http://webapp-v2:3000"

TCP/UDP Services

Database Proxy

services:
  postgres:
    image: postgres:15
    labels:
      - "traefik.enable=true"
      - "traefik.tcp.routers.postgres.rule=HostSNI(`*`)"
      - "traefik.tcp.routers.postgres.entrypoints=postgres"
      - "traefik.tcp.services.postgres.loadbalancer.server.port=5432"

# Add to traefik.yml entryPoints:
# postgres:
#   address: ":5432"

WebSocket Support

services:
  websocket-server:
    image: ws-server:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.ws.rule=Host(`ws.example.com`)"
      - "traefik.http.routers.ws.entrypoints=websecure"
      - "traefik.http.routers.ws.tls.certresolver=letsencrypt"
      # WebSocket specific
      - "traefik.http.services.ws.loadbalancer.server.port=8080"
      - "traefik.http.services.ws.loadbalancer.sticky.cookie=true"

Monitoring and Metrics

Prometheus Metrics

# traefik.yml
metrics:
  prometheus:
    buckets:
      - 0.1
      - 0.3
      - 1.2
      - 5.0
    addEntryPointsLabels: true
    addServicesLabels: true
    addRoutersLabels: true
    entryPoint: metrics

entryPoints:
  metrics:
    address: ":8082"

Access Logs

# traefik.yml
accessLog:
  filePath: "/var/log/traefik/access.log"
  format: json
  bufferingSize: 100
  filters:
    statusCodes:
      - "200-299"
      - "400-599"
    retryAttempts: true
    minDuration: "10ms"
  fields:
    defaultMode: keep
    names:
      ClientUsername: drop
    headers:
      defaultMode: keep
      names:
        User-Agent: keep
        Authorization: drop

Production Best Practices

1. Secure Docker Socket

services:
  docker-proxy:
    image: tecnativa/docker-socket-proxy
    container_name: docker-proxy
    environment:
      CONTAINERS: 1
      SERVICES: 1
      TASKS: 1
      NETWORKS: 1
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - proxy

  traefik:
    depends_on:
      - docker-proxy
    environment:
      - DOCKER_HOST=tcp://docker-proxy:2375
    # Remove docker.sock volume

2. Resource Limits

services:
  traefik:
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 256M
        reservations:
          cpus: '0.25'
          memory: 128M

3. Certificate Management

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

4. Error Pages

# traefik/dynamic/errors.yml
http:
  middlewares:
    error-pages:
      errors:
        status:
          - "500-599"
        service: error-pages@docker
        query: "/{status}.html"

  # Custom error pages service
  services:
    error-pages:
      loadBalancer:
        servers:
          - url: "http://error-pages:8080"

Complete Production Example

# docker-compose.yml
version: '3.8'

services:
  traefik:
    image: traefik:v3.0
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    ports:
      - "80:80"
      - "443:443"
    environment:
      - TZ=UTC
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro
      - ./traefik/dynamic:/etc/traefik/dynamic:ro
      - ./traefik/acme.json:/acme.json
      - ./traefik/logs:/var/log/traefik
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      # Dashboard
      - "traefik.http.routers.traefik.rule=Host(`traefik.example.com`)"
      - "traefik.http.routers.traefik.entrypoints=websecure"
      - "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
      - "traefik.http.routers.traefik.service=api@internal"
      - "traefik.http.routers.traefik.middlewares=auth@file"
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 256M

networks:
  proxy:
    name: proxy
    driver: bridge

Conclusion

Traefik simplifies reverse proxy configuration for Docker:

  • Automatic discovery: Services register themselves
  • Dynamic configuration: No restarts needed
  • Built-in HTTPS: Let’s Encrypt integration
  • Modern dashboard: Visual monitoring
  • Production-ready: Load balancing, health checks, metrics

Start with a simple setup and add features as needed.


Running Traefik in production? Share your configuration tips!

Advertisement

In-Article Ad

Dev Mode

Share this article

Mahmoud DEVO

Mahmoud DEVO

Senior Full-Stack Developer

I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.

Enjoyed this article?

Subscribe to get more tech content delivered to your inbox.

Related Articles