Traefik

Traefik to reverse proxy i load balancer zaprojektowany pod kontenery. Nasłuchuje na portach 80/443, terminuje TLS, routuje ruch po nagłówku Host do odpowiedniego backendu. Certyfikaty Let's Encrypt odnawiane automatycznie przez HTTP challenge.

Dwa providery konfiguracji:

  • Docker provider (exposedbydefault: false) do auto-discovery kontenerów z labelami Traefik.
  • File provider (directory: /etc/traefik/dynamic, watch: true) z plikami YAML per serwis. Zmiany w katalogu dynamic/ przeładowują routing bez restartu kontenera.

W tej konfiguracji routing oparty wyłącznie na file providerze. Docker provider wyłączony (watch: false, exposedbydefault: false), służy tylko do inspekcji kontenerów w dashboardzie.

Architektura

graph LR
    I[Internet<br/>:80 / :443] --> T[Traefik<br/>reverse proxy]
    T -->|authelia-auth| AUTH[Authelia<br/>:8080]
    T --> BLOG[Blog<br/>:5000]
    T --> ANALYTICS[Plausible<br/>:8000]
    T -->|authelia-auth| DOZZLE[Dozzle<br/>:8080]
    T -->|authelia-auth| WEBMIN[Webmin<br/>:10000]
    T -->|authelia-auth| HOMER[Homer<br/>:8080]
    T -->|authelia-auth| DASH[Dashboard<br/>api-internal]
    T --> APPS[Inne serwisy...]

Konfiguracja

docker-compose.yml

services:
  traefik:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped
    ports: [80:80, 443:443]
    extra_hosts:
      - "host.docker.internal:host-gateway"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./config.yaml:/etc/traefik/traefik.yml:ro
      - ./dynamic:/etc/traefik/dynamic:ro
      - ./letsencrypt:/letsencrypt
    networks: [traefik_network]

networks:
  traefik_network:
    external: true

traefik_network to zewnętrzna sieć Docker (docker network create traefik_network). Wszystkie serwisy wystawiane przez Traefik muszą być w tej sieci. Volume letsencrypt przechowuje acme.json z certyfikatami. host.docker.internal:host-gateway umożliwia proxy do usług działających bezpośrednio na hoście (np. Webmin na porcie 10000).

config.yaml (statyczna konfiguracja)

global:
  checkNewVersion: true
  sendAnonymousUsage: false

api:
  debug: false
  insecure: false
  dashboard: true

log:
  level: INFO

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    watch: false
    exposedbydefault: false

  file:
    directory: /etc/traefik/dynamic
    watch: true

entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"

certificatesResolvers:
  letsencrypt:
    acme:
      email: "<email>"
      storage: "/letsencrypt/acme.json"
      httpChallenge:
        entryPoint: web

api.insecure: false wyłącza dostęp do dashboardu na porcie 8080. Dashboard wystawiony jako router api@internal za Authelią (poniżej). httpChallenge wymaga otwartego portu 80 od zewnątrz.

Struktura katalogu dynamic/

Każdy plik YAML w dynamic/ definiuje router + serwis dla jednej domeny. Traefik monitoruje katalog (watch: true) i przeładowuje konfigurację po każdej zmianie pliku.

dynamic/
├── home.yaml              # redirect HTTP→HTTPS + ACME challenge
├── middlewares.yaml        # definicja middleware authelia-auth
├── auth.yaml              # auth.robertolechowski.com → Authelia
├── traefik.yaml           # traefik.robertolechowski.com → dashboard
├── block_php.yaml         # blokowanie .php, .tar, .zip, wp-*
├── blog_robertolechowski_com.yaml
├── analytics.yaml
├── dozzle.yaml
├── homer.yaml
├── minio.yaml
├── webmin.yaml
├── whoami.yaml
├── robertolechowski_com.yaml
├── 3d_generator.yaml
├── k2000.yaml
├── mandelbro.yaml
├── reverseengineering_pl.yaml
└── siquijor_pl.yaml

Redirect HTTP → HTTPS

Plik home.yaml obsługuje dwie rzeczy: przepuszczenie ACME challenge na porcie 80 (priorytet 2000, żeby Let's Encrypt mógł zweryfikować domenę) i przekierowanie całego pozostałego ruchu HTTP na HTTPS (priorytet 1, łapie wszystko co nie pasuje do wyższych priorytetów).

http:
  routers:
    acme_challenge:
      rule: "PathPrefix(`/.well-known/acme-challenge/`)"
      entryPoints: [web]
      priority: 2000
      service: noop@internal

    http-catchall:
      rule: "PathPrefix(`/`)"
      entryPoints: [web]
      priority: 1
      middlewares: [redirect-to-https]
      service: noop@internal

  middlewares:
    redirect-to-https:
      redirectScheme:
        scheme: https
        permanent: true

Middleware Authelia

Plik middlewares.yaml definiuje middleware authelia-auth typu forwardAuth. Każdy request trafia najpierw do Authelia (/api/verify). Jeśli sesja jest ważna, request przechodzi dalej z nagłówkami Remote-User, Remote-Groups, Remote-Name, Remote-Email. Jeśli nie, redirect na stronę logowania auth.robertolechowski.com.

http:
  middlewares:
    authelia-auth:
      forwardAuth:
        address: "http://authelia:8080/api/verify?rd=https://auth.robertolechowski.com"
        trustForwardHeader: true
        authResponseHeaders:
          - Remote-User
          - Remote-Groups
          - Remote-Name
          - Remote-Email
        maxResponseBodySize: 1048576

Sam router Authelia (auth.yaml) nie ma middleware authelia-auth (circular dependency):

http:
  routers:
    auth:
      rule: "Host(`auth.robertolechowski.com`)"
      entryPoints: [websecure]
      tls:
        certResolver: letsencrypt
      service: auth-service

  services:
    auth-service:
      loadBalancer:
        servers:
          - url: "http://authelia:8080/"

Blokowanie .php i WordPress scan

Boty ciągle skanują pod .php, wp-admin, wp-includes, wp-content. Router block_php_1 z priorytetem 1000 łapie te requesty na obu entrypoints i zwraca 403 (serwis deny_403 to kontener zwracający stały kod 403).

http:
  routers:
    block_php_1:
      rule: >
        PathRegexp(`^.*\.php(?:$|/)`) ||
        PathRegexp(`^.*\.(tar|zip)$`) ||
        PathRegexp(`(?i).*wp-includes.*`) ||
        PathRegexp(`(?i).*wp-admin.*`) ||
        PathRegexp(`(?i).*wp-content.*`)
      entryPoints: [web, websecure]
      priority: 1000
      service: deny_403

  services:
    deny_403:
      loadBalancer:
        servers:
          - url: "http://http_403:80"

Wzorzec routera — przykłady

Każdy plik w dynamic/ ma tę samą strukturę: router z Host() rule, entrypoint websecure, TLS z certresolver: letsencrypt, opcjonalnie middleware, i serwis z adresem backendu.

Serwis publiczny (bez Authelia):

# blog_robertolechowski_com.yaml
http:
  routers:
    app_blog:
      rule: "Host(`blog.robertolechowski.com`)"
      entryPoints: [websecure]
      tls:
        certresolver: letsencrypt
      service: blog-service

  services:
    blog-service:
      loadBalancer:
        servers:
          - url: "http://blog_robertolechowski_com:5000/"

Serwis chroniony Authelią:

# dozzle.yaml
http:
  routers:
    dozzle:
      rule: "Host(`dozzle.robertolechowski.com`)"
      entryPoints: [websecure]
      tls:
        certresolver: letsencrypt
      middlewares:
        - authelia-auth
      service: dozzle-service

  services:
    dozzle-service:
      loadBalancer:
        servers:
          - url: "http://dozzle:8080/"

Proxy do usługi na hoście (Webmin nie działa w Dockerze):

# webmin.yaml
http:
  routers:
    webmin:
      rule: "Host(`webmin.robertolechowski.com`)"
      entryPoints: [websecure]
      tls:
        certresolver: letsencrypt
      middlewares:
        - webmin-headers
        - authelia-auth
      service: webmin-service

  services:
    webmin-service:
      loadBalancer:
        servers:
          - url: "http://host.docker.internal:10000/"

  middlewares:
    webmin-headers:
      headers:
        customRequestHeaders:
          X-Forwarded-Proto: "https"
        sslProxyHeaders:
          X-Forwarded-Proto: "https"

Dashboard Traefik (api@internal to wbudowany serwis):

# traefik.yaml
http:
  routers:
    traefik-dashboard:
      rule: "Host(`traefik.robertolechowski.com`)"
      entryPoints: [websecure]
      tls:
        certresolver: letsencrypt
      middlewares:
        - authelia-auth
      service: api@internal

Dodawanie nowego serwisu

  1. Utworzyć plik dynamic/<nazwa>.yaml z routerem i serwisem (wzorzec powyżej).
  2. Kontener backendu musi być w sieci traefik_network.
  3. Jeśli serwis wymaga logowania: dodać middlewares: [authelia-auth] do routera.
  4. Traefik automatycznie wykryje nowy plik i zamówi certyfikat Let's Encrypt przy pierwszym requeście.