# Traefik

Reverse proxy, která se konfiguruje sama podle štítků kontejnerů. Když ti služby často přibývají a ubývají, ušetří to spoustu ruční práce — a když máš tři stabilní kontejnery, je to zbytečná složitost oproti [Caddy](Caddy).

## V čem je jiná

Nemá konfigurační soubor pro jednotlivé weby. Sleduje Docker socket a když se objeví kontejner se správnými štítky, začne ho obsluhovat. Když zmizí, přestane.

Cena za to jsou dvě věci: **potřebuje přístup k Docker socketu** (což je bezpečnostně citlivé) a **terminologie je vlastní** — entrypoint, router, service, middleware. Než to jednou pochopíš, chvíli to trvá.

## Základní nasazení

`compose.yml`:

```yaml
services:
  traefik:
    image: traefik:v3
    restart: unless-stopped
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --entrypoints.web.http.redirections.entrypoint.to=websecure
      - --entrypoints.web.http.redirections.entrypoint.scheme=https
      - --certificatesresolvers.le.acme.email=ty@example.cz
      - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - letsencrypt:/letsencrypt
    networks: [web]

volumes:
  letsencrypt:

networks:
  web:
```

`exposedbydefault=false` je důležité. Bez něj Traefik zveřejní každý kontejner, který uvidí.

## Přidání služby

```yaml
  immich:
    image: ghcr.io/immich-app/immich-server
    networks: [web]
    labels:
      - traefik.enable=true
      - traefik.http.routers.immich.rule=Host(`foto.example.cz`)
      - traefik.http.routers.immich.entrypoints=websecure
      - traefik.http.routers.immich.tls.certresolver=le
      - traefik.http.services.immich.loadbalancer.server.port=2283
```

Žádné `ports`, žádný zásah do konfigurace proxy. Kontejner musí být ve stejné síti jako Traefik.

To `loadbalancer.server.port` je port **uvnitř** kontejneru. Traefik jde přímo na něj, publikovaný port nepotřebuje. Souvislosti v [sítích v Dockeru](Site-v-Dockeru).

## Slovníček Traefiku

| Pojem | Co to je |
|---|---|
| entrypoint | port, na kterém Traefik poslouchá (`:80`, `:443`) |
| router | pravidlo, co kam patří (podle `Host`, cesty, hlavičky) |
| service | cíl, kam se to pošle |
| middleware | úprava po cestě (autentizace, hlavičky, přesměrování) |
| provider | odkud se bere konfigurace (Docker, soubor, Kubernetes) |

## Middleware

Autentizace před službou bez vlastního přihlašování:

```yaml
labels:
  - traefik.http.middlewares.auth.basicauth.users=martin:$$2y$$10$$hash
  - traefik.http.routers.app.middlewares=auth
```

V Compose se `$` píše jako `$$`, jinak ho Compose sežere.

Omezení podle IP:

```yaml
labels:
  - traefik.http.middlewares.jenlan.ipallowlist.sourcerange=192.168.1.0/24,10.10.0.0/24
  - traefik.http.routers.proxmox.middlewares=jenlan
```

Bezpečnostní hlavičky:

```yaml
labels:
  - traefik.http.middlewares.sec.headers.stsSeconds=31536000
  - traefik.http.middlewares.sec.headers.contentTypeNosniff=true
  - traefik.http.middlewares.sec.headers.frameDeny=true
```

Middleware se dají řetězit oddělené čárkou.

## Wildcard certifikát

Přes DNS-01, s tokenem od DNS poskytovatele:

```yaml
    command:
      - --certificatesresolvers.le.acme.dnschallenge=true
      - --certificatesresolvers.le.acme.dnschallenge.provider=cloudflare
    environment:
      - CF_DNS_API_TOKEN=${CF_TOKEN}
```

```yaml
labels:
  - traefik.http.routers.app.tls.domains[0].main=example.cz
  - traefik.http.routers.app.tls.domains[0].sans=*.example.cz
```

Viz [certifikáty a Let's Encrypt](Certifikaty-a-Lets-Encrypt).

## Služby mimo Docker

Pro věci, které v kontejneru neběží (Proxmox, router, NAS), použij souborový provider:

```yaml
    command:
      - --providers.file.directory=/etc/traefik/dynamic
    volumes:
      - ./dynamic:/etc/traefik/dynamic:ro
```

`dynamic/proxmox.yml`:

```yaml
http:
  routers:
    proxmox:
      rule: "Host(`pve.example.cz`)"
      entrypoints: [websecure]
      service: proxmox
      tls:
        certresolver: le
  services:
    proxmox:
      loadBalancer:
        servers:
          - url: "https://192.168.1.5:8006"
        serversTransport: skipverify
  serversTransports:
    skipverify:
      insecureSkipVerify: true
```

To `insecureSkipVerify` je nutné, protože Proxmox má certifikát podepsaný sám sebou. Čistší, byť pracnější řešení je vystavit mu certifikát z [vlastní CA](Vlastni-certifikacni-autorita) a ověřování nechat zapnuté.

## Dashboard

```yaml
    command:
      - --api.dashboard=true
    labels:
      - traefik.http.routers.dash.rule=Host(`traefik.example.cz`)
      - traefik.http.routers.dash.service=api@internal
      - traefik.http.routers.dash.middlewares=auth,jenlan
```

Dashboard je užitečný při ladění, ale **nikdy ho nevystavuj bez autentizace**. Ukazuje kompletní topologii tvých služeb.

## Bezpečnostní poznámka k Docker socketu

Přístup k `/var/run/docker.sock` znamená v praxi root na hostiteli. Kdokoliv, kdo prolomí Traefik, ovládne stroj. Připojení `:ro` na tom nic nemění, protože socket je API, ne soubor.

Kdo to bere vážně, dá mezi Traefik a socket proxy (`tecnativa/docker-socket-proxy`), která propustí jen čtení kontejnerů.

Pro homelab je to přijatelné riziko, ale stojí za to o něm vědět. [Caddy](Caddy) tenhle problém nemá.

## Diagnostika

```bash
docker compose logs -f traefik
docker inspect nazev-kontejneru | grep -A20 Labels
```

| Problém | Příčina |
|---|---|
| Kontejner Traefik nevidí | chybí `traefik.enable=true` nebo není ve stejné síti |
| `404 page not found` | pravidlo `Host()` nesedí, nebo chybí router |
| `Bad Gateway` | špatný `loadbalancer.server.port` |
| Certifikát se nevydá | `acme.json` nemá práva `600`, nebo port 80 není dostupný |

Ta práva na `acme.json` jsou klasika — Traefik odmítne soubor s volnějšími právy použít a v logu to napíše nenápadně.
