76 lines
3.1 KiB
Markdown
76 lines
3.1 KiB
Markdown
# Traefik Edge Stack
|
||
|
||
Reverse proxy for everything on this host. The goal: keep Cloudflare in front, expose a private LAN entrypoint, and let Docker stacks self-register through labels without leaking secrets.
|
||
|
||
## Architecture Snapshot
|
||
|
||
- **Static config (`traefik.yml`)**
|
||
- EntryPoints: `web/websecure` on `10.0.0.225`, `internal_web/internal_websecure` on `192.168.50.4`.
|
||
- Trusted IP lists are managed by `scripts/update_cloudflare_ips.py`.
|
||
- Docker provider is discovery-only; every container opts in with labels.
|
||
- File provider loads everything in `dynamic.d/`.
|
||
|
||
- **Dynamic config (`dynamic.d/`)**
|
||
- `middlewares/` – retry, compression, CrowdSec (rendered from template).
|
||
- `transports/fast-upstreams.yml` – shared connection pool tuning.
|
||
- `routers/` – internal-only routers (public ones stay in labels).
|
||
|
||
- **Host networking**
|
||
Traefik runs with `network_mode: host` so it can bind to both IPs simultaneously. Switching to bridge mode would require duplicating Traefik or adding another L4 hop, so host mode stays.
|
||
|
||
## Secrets Workflow
|
||
|
||
1. Copy `.env.example` → `.env` and fill:
|
||
- `CLOUDFLARE_EMAIL`, `CLOUDFLARE_DNS_API_TOKEN`
|
||
- `CROWDSEC_LAPI_KEY`
|
||
2. Render secret-aware dynamic files:
|
||
```bash
|
||
./scripts/render_dynamic.sh
|
||
```
|
||
This uses `templates/crowdsec.yml.tmpl` and writes `dynamic.d/middlewares/crowdsec.yml` (ignored by git).
|
||
|
||
## Runbook
|
||
|
||
```bash
|
||
# start / update Traefik
|
||
docker compose up -d traefik
|
||
|
||
# refresh Cloudflare IPs and restart safely
|
||
python scripts/update_cloudflare_ips.py
|
||
|
||
# tail logs
|
||
tail -f traefik.log
|
||
tail -f access.log
|
||
```
|
||
|
||
Rotate CrowdSec keys? Edit `.env`, rerun `render_dynamic.sh`, then `docker compose up -d traefik`.
|
||
|
||
## Service Labels Cheat Sheet
|
||
|
||
```yaml
|
||
labels:
|
||
- traefik.enable=true
|
||
- traefik.http.routers.myapp.rule=Host(`app.example.com`)
|
||
- traefik.http.routers.myapp.entrypoints=websecure
|
||
- traefik.http.routers.myapp.tls.certresolver=letsencrypt
|
||
- traefik.http.routers.myapp.middlewares=crowdsec@file,retry-fast@file,compress-middleware@file
|
||
- traefik.http.services.myapp.loadbalancer.serversTransport=fast-upstreams@file
|
||
- traefik.http.services.myapp.loadbalancer.server.port=3000
|
||
- traefik.docker.network=traefik_default
|
||
```
|
||
|
||
Most stacks use the same middleware chain: CrowdSec bouncer (plugin), retry, and compression. Internal-only services skip CrowdSec by pointing at the `internal_*` entrypoints.
|
||
|
||
## Performance Notes
|
||
|
||
- Access logs are buffered with headers trimmed to keep syscalls down.
|
||
- Compression enforces a 1 KB minimum and respects the client’s preferred encoding.
|
||
- Shared transport keeps 64 idle connections per backend with aggressive idle/response timeouts.
|
||
- `retry-fast` retries once after 50 ms, smoothing transient Puma/Node hiccups without hammering backends.
|
||
|
||
## Things to Remember
|
||
|
||
- Watchtower is still enabled for Traefik; pin the image tag when you need deterministic upgrades.
|
||
- `scripts/update_cloudflare_ips.py` rewrites the static trusted IP block and restarts Traefik—run it via cron.
|
||
- Dashboard auth is intentionally disabled because access only happens from the LAN entrypoint. If that changes, re-enable `basicauth`.
|