Files
homelab/docker-compose/traefik/README.md
2026-08-27 12:07:31 +03:30

6.7 KiB

Traefik

Self-hosted reverse proxy replacing Nginx Proxy Manager — gives every service a friendly <service>.home.server hostname with locally-trusted HTTPS, configured entirely as code.

Why the migration from Nginx Proxy Manager

NPM works fine, but the switching cost grew as the homelab did. Two things pushed the move:

1. Infrastructure as code

NPM's entire configuration lives behind a web UI — proxy hosts, SSL certificates, custom locations, access rules. None of it is version-controlled, exportable, or reproducible. If the container dies and comes back without its data volume, you're rebuilding every proxy host by hand.

Traefik is the opposite: every route, middleware, TLS cert, and entrypoint is defined in YAML files that live in this repo. The compose file, the dynamic/ configs, the cert files — all version-controlled, all diffable, all reproducible with docker compose up -d. This is the same pattern the rest of the homelab already follows (each service is a Compose file in a folder), and Traefik fits that model naturally where NPM didn't.

2. Docker daemon auto-discovery

NPM requires you to manually add each proxy host through its web UI — type the domain, point it at the container's IP and port, attach an SSL cert, save. Every new service means another round-trip through the UI.

Traefik talks directly to the Docker daemon and watches for containers with traefik.enable=true. Add a few labels to any compose file and Traefik picks up the new route automatically — no UI interaction, no restart, no manual configuration. This means adding a new service to the reverse proxy is a two-line addition to that service's compose file, not a separate step in a different tool.

How it works

traefik/
├── compose.yaml          # Traefik container + static config
├── certs/                # Local TLS certificates (mkcert)
│   ├── home.server.crt
│   └── home.server.key
└── dynamic/              # File-based provider — routes, middlewares, TLS
    ├── tls.yaml          # Default TLS cert store
    ├── middlewares.yaml  # HTTPS redirect + security headers
    ├── pihole.yaml       # Pi-hole route
    ├── plex.yml          # Plex route (external host)
    ├── sentinel.yml      # Sentinel route (external host)
    └── dockscope-auth.yml # Basic auth middleware for DockScope

Static config (compose.yaml)

Traefik's static configuration is passed as command-line flags:

  • Docker provider — watches the Docker daemon for labeled containers on the web_net network
  • File provider — watches /dynamic/ for YAML config changes (hot-reloaded, no restart needed)
  • Entrypoints — :80 (HTTP) and :443 (HTTPS)
  • API dashboard — enabled but not insecure (accessed via its own HTTPS route)

Dynamic config (dynamic/)

Routes and middlewares are defined as YAML files, not labels. This keeps the compose files clean and makes it easy to see all routing rules in one place:

  • middlewares.yaml — shared redirect-to-https and security-headers middlewares (HSTS, XSS filter, content-type nosniff, SAMEORIGIN framing)
  • tls.yaml — default TLS cert store pointing at the mkcert-generated certificate
  • pihole.yaml, plex.yml, sentinel.yml — per-service route definitions for services that can't use Docker labels (host-network or external hosts)

Services using Docker labels

Most services define their Traefik routes directly in their compose files via labels:

labels:
  - "traefik.enable=true"
  - "traefik.docker.network=web_net"
  - "traefik.http.routers.<service>.rule=Host(`<service>.home.server`)"
  - "traefik.http.routers.<service>.entrypoints=websecure"
  - "traefik.http.routers.<service>.tls=true"
  - "traefik.http.routers.<service>.middlewares=security-headers@file"
  - "traefik.http.routers.<service>-http.rule=Host(`<service>.home.server`)"
  - "traefik.http.routers.<service>-http.entrypoints=web"
  - "traefik.http.routers.<service>-http.middlewares=redirect-to-https@file"
  - "traefik.http.services.<service>.loadbalancer.server.port=<internal-port>"

Services currently fronted by Traefik:

Service Hostname Internal Port
Prometheus prometheus.home.server 9090
Grafana grafana.home.server 3000
cAdvisor cadvisor.home.server 8080
Loki loki.home.server 3100
Alloy alloy.home.server 12345
Pi-hole pihole.home.server 8082 (host)
Portainer portainer.home.server 9000
n8n n8n.home.server 5678
Filebrowser filebrowser.home.server 80
DockScope dockscope.home.server 4681
Semaphore semaphore.home.server 3000
MinIO minio.home.server 9001
Plex plex.home.server 32400 (external)
Sentinel sentinel.home.server 8088 (external)
Vaultwarden vaultwarden.home.server 80
Traefik Dashboard traefik.home.server API@internal

TLS certificates

Same approach as before — mkcert generates a local CA and signs certificates for *.home.server. The cert and key live in certs/ and are mounted read-only into the Traefik container.

Traefik loads the default cert via dynamic/tls.yaml:

tls:
  stores:
    default:
      defaultCertificate:
        certFile: /certs/home.server.crt
        keyFile: /certs/home.server.key

Adding a new service with a .home.server hostname automatically gets the right cert — no per-host certificate assignment needed.

Adding a new service

  1. If the service is a Docker container on web_net — add Traefik labels to its compose file (copy the pattern from any existing service above)
  2. If the service is host-network or external — create a new YAML file in dynamic/ with the router and service definition
  3. Add the hostname to Pi-hole — so DNS resolves <service>.home.server to the server's LAN IP

No restart required — Traefik watches both the Docker daemon and the dynamic/ directory and picks up changes automatically.

Network

All Traefik-fronted services join the web_net external Docker network. This network must be created before starting Traefik:

docker network create web_net

Security notes

  • The Traefik dashboard is behind its own HTTPS route with security headers — not exposed on the public internet, LAN-only via Pi-hole DNS
  • DockScope additionally uses a basicAuth middleware (dynamic/dockscope-auth.yml) since it mounts the Docker socket
  • All HTTP requests are redirected to HTTPS permanently (301)
  • Security headers include HSTS (1 year, includeSubdomains, preload), XSS filter, content-type nosniff, and SAMEORIGIN framing

← Back to Home