My Traefik Setup

This post, as most of my posts tend to be, is my attempt at documenting how I set something up so that future Jeff can do it again, or troubleshoot it when it inevitable breaks at some point in the, hopefully, distant future.

On my various web servers, I’ve been moving more and more of my content into Docker containers to, hopefully, aid in isolation (both security and unintended interactions/interdependencies) and maintainability. As such, I’ve decided to use Traefik to proxy requests through to the underlying containers, and to handle all the TLS stuff (getting certificates from Let’s Encrypt, A+ on both SSLLabs and SecureHeaders).

Traefik Setup

The following docker-compose.yml file sets up the Traefik container.

version: "3.7"

services:
  traefik:
    image: "traefik:latest"
    container_name: "traefik"
    hostname: "traefik"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      # Traefic needs access to docker information to detect containers/read labels
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      # Additional configuration for Traefik
      - "./traefik.yml:/traefik.yml:ro"
      - "./traefik-dynamic.yml:/traefik-dynamic.yml:ro"
      # Storage for Let's Encrypt
      - "./acme.json:/acme.json"
    labels:
      - "traefik.enable=true"

networks:
  default:
    external:
      name: traefik_net

Here is the traefik.yml file.

log:
  level: INFO

api:
  insecure: true
  dashboard: false  # I've turned off the dashboard in my environment

entryPoints:
  web:
    address: ":80"
    http:
      redirections:  # always redirect HTTP to HTTPS
        entryPoint:
          to: websecure
          scheme: https

  websecure:
    address: ":443"
    http:
            middlewares:
                    # middleware defined in traefic-dyanmic.yml
                    # applies security headers to all HTTPS traffic
                    - secHeaders@file   

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
  file:
    filename: "/traefik-dynamic.yml"

# Setup Traefik to obtain TLS certificates from Let's Encrypt
# Using web challenge (no wildcard certs, unfortunately) because it's easy
certificatesResolvers:
  lets-encr:
    acme:
      storage: acme.json
      email: admin@domain.com
      httpChallenge:
        entryPoint: web

Here is traefic-dynamic.yml which defines middleware for Security Headers, and apex > www redirections.

tls:
  options:
    default:
      minVersion: VersionTLS12
      cipherSuites:
        - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
        - TLS_AES_128_GCM_SHA256
        - TLS_AES_256_GCM_SHA384
        - TLS_CHACHA20_POLY1305_SHA256
      curvePreferences:
        - CurveP521
        - CurveP384
      sniStrict: true
http:
  middlewares:
    redirect-non-www-to-www:
      redirectregex:
        permanent: true
        regex: "^https?://(?:www\\.)?(.+)"
        replacement: "https://www.${1}"

    redirect-www-to-non-www:
      redirectregex:
        permanent: true
        regex: "^https?://www\\.(.+)"
        replacement: "https://${1}"

    secHeaders:
      headers:
        browserXssFilter: true
        contentTypeNosniff: true
        frameDeny: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 15768000
        contentSecurityPolicy: "upgrade-insecure-requests"
        referrerPolicy: "no-referrer-when-downgrade"
        permissionsPolicy: "interest-cohort=()"

Sample Container 1: Single Domain

The following docker-compose.yml sets up a basic Whoami service on whoami.domain.com. It will be served via HTTPS (certificate automatically received from Let’s Encrypt). It should get A+ on both SecureHeaders and SSLLabs due to the additional headers and adjusted cipher suites.

version: "3.7"

services:
  whoami:
    image: "containous/whoami"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami1.entrypoints=websecure"
      - "traefik.http.routers.whoami1.rule=Host(`whoami.domain.com`)"
      - "traefik.http.routers.whoami1.tls.certresolver=lets-encr"

networks:
  default:
    external:
      name: traefik_net

Once Traefik is up and running, a single docker-compose up -d should start this container, and it’ll automatically be picked up by Traefik and served on whoami.domain.com via. TLS.

Sample Container 2: www. and redirect from apex

This docker-compose.yml sets up the same Whoami service but serves it on www.domain.com. It also redirects from the apex domain domain.com to the canonical domain www.domain.com (with valid TLS certificates for both).

version: '3.8'

services:

  server:
    restart: always
    image: index.docker.io/jclement/blog:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami2.entrypoints=websecure"
      - "traefik.http.routers.whoami2.rule=Host(`www.domain.com`,`domain.com`)"
      - "traefik.http.routers.whoami2.tls.certresolver=lets-encr"
      - "traefik.http.routers.whoami2.middlewares=redirect-non-www-to-www@file"

networks:
  default:
    external:
      name: traefik_net