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