Files

218 lines
8.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Traefik reverse proxy stack
Docker Compose stack for [Traefik](https://traefik.io/) with Lets Encrypt (Cloudflare DNS challenge), a secured dashboard, and a reusable middleware library for security headers, CSP tiers, Authentik SSO, redirects, and IP allowlists.
## Prerequisites
- Docker Engine with Compose v2
- A Cloudflare account with API access for the domains you terminate here
- DNS for those domains pointed at the host that runs this stack
- A shared Docker network that your backend services also join (default: `docknet`)
## Quick start
```bash
git clone <this-repo> traefik && cd traefik
cp example.env .env
```
Edit `.env` (see [Environment variables](#environment-variables)), then:
```bash
docker compose --env-file .env up -d
```
Traefik will join (or create) the Docker network named by `NETWORK_NAME`, watch containers on that host for Traefik labels, and issue certificates via Cloudflare DNS. ACME data is stored in the Docker named volume `traefik-acme` (created automatically; no manual file setup).
## Layout
```
.
├── compose.yml # Traefik service, ports, dashboard labels, ACME volume
├── example.env # Env template (copy to .env)
└── traefik/
├── traefik.yaml # Static config: entrypoints, providers, TLS SANs
└── conf.d/
├── middlewares.yaml # Shared middleware library
├── routers.yaml # Optional file-based routers
└── services.yaml # Optional file-based services
```
Static config lives in `traefik/traefik.yaml`. Dynamic config (middlewares, optional routers/services) is loaded from `traefik/conf.d/` and hot-reloads when files change. Certificate state lives in the `traefik-acme` volume, not in the repo.
## Environment variables
Copy `example.env` to `.env` and fill in real values:
| Variable | Purpose |
| --- | --- |
| `CLOUDFLARE_EMAIL` | Cloudflare account email (ACME + API) |
| `CLOUDFLARE_API_KEY` | Cloudflare Global API Key (or scoped token with DNS edit) |
| `CONSOLE_HOST` | Hostname for the Traefik dashboard (e.g. `traefik.example.com`) |
| `NETWORK_NAME` | Docker network name shared with backends (default `docknet`) |
| `TRAEFIK_CONSOLE_AUTH` | Basic-auth users for the dashboard (`htpasswd` format) |
| `TRAEFIK_YAML` | Host path to static config (default `./traefik/traefik.yaml`) |
| `TRAEFIK_CONFD` | Host path to dynamic config dir (default `./traefik/conf.d`) |
Generate dashboard credentials:
```bash
# Install apache2-utils / httpd-tools if needed, then:
htpasswd -nbB admin 'your-strong-password'
```
Paste the output into `TRAEFIK_CONSOLE_AUTH`. If the value contains `$`, escape them for Compose (use `$$`) or quote carefully in `.env`.
## Ports
| Host | Container | Role |
| --- | --- | --- |
| `8342` | `80` | HTTP → redirects to HTTPS |
| `8767` | `443` | HTTPS |
| `8080` | `8080` | Traefik API / dashboard (also exposed via `CONSOLE_HOST` with TLS + basic auth) |
Point your public reverse path or firewall at `8767` (and `8342` if you want HTTP→HTTPS at the edge). Adjust the left-hand ports in `compose.yml` if you prefer `80`/`443` on the host.
## TLS and certificates
- Challenge: **DNS-01** via Cloudflare (`certificatesResolvers.letsencrypt`)
- Storage: Docker named volume `traefik-acme` mounted at `/letsencrypt` (Traefik creates `acme.json` on first run)
- Default certificate SANs for the TLS store are defined under `tls.stores.default` in `traefik/traefik.yaml` — edit that list for your domains
The volume survives `docker compose down` / `up`. Prefer `docker compose down` over `docker compose down -v` unless you intentionally want to wipe certs (which will re-hit Lets Encrypt rate limits).
Ensure Cloudflare credentials can create/delete `_acme-challenge` TXT records for every zone you serve.
## Dashboard
The dashboard is enabled and routed with Docker labels on the Traefik container:
- URL: `https://<CONSOLE_HOST>/`
- Entrypoint: `websecure`
- Auth: middleware `traefik-auth` (basic auth from `TRAEFIK_CONSOLE_AUTH`)
- Certificate: Lets Encrypt via `letsencrypt` resolver
## Using middlewares on your services
Connect apps to the same Docker network (`NETWORK_NAME`), enable Traefik, and attach middlewares by name with the `@file` provider:
```yaml
services:
myapp:
image: myapp:latest
networks:
- docknet
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=security-supermax@file,gzip@file
- traefik.http.services.myapp.loadbalancer.server.port=8080
networks:
docknet:
external: true
name: docknet
```
Chain multiple middlewares with commas. File-provider names resolve as `<name>@file`.
## Middleware catalog
Defined in `traefik/conf.d/middlewares.yaml`.
### Security chains (pick one)
| Middleware | Intent |
| --- | --- |
| `default` | CSP strict + frame deny + gzip + HSTS + baseline security headers |
| `secure-all` | Permissive CSP + same-origin frames + HSTS + security headers |
| `security-mallcop` | Open referrer + open CSP + HSTS + security headers |
| `security-lax` | Lax referrer + lax CSP + HSTS + security headers |
| `security-medium` | Permissive referrer/CSP + same-origin frames + HSTS + security headers |
| `security-prison` | Medium referrer + balanced CSP + same-origin frames + HSTS + security headers |
| `security-supermax` | No-referrer + strict CSP + frame deny + HSTS + security headers |
| `security-supermax-with-analytics` | Supermax plus GA/Clarity/Umami-friendly CSP and COOP |
### CSP building blocks
| Middleware | Notes |
| --- | --- |
| `csp-open-header` | Very permissive (legacy / hard-to-lock apps) |
| `csp-lax-header` | Self + common inline/eval allowances |
| `csp-permissive-header` | Self-focused with inline/eval for scripts/styles |
| `csp-balanced-header` | Self + common CDNs (jsDelivr, cdnjs, unpkg, Google Fonts) |
| `csp-strict-header` | Self-only |
| `csp-strict-header-with-ga` | Strict plus analytics/Clarity/GTM endpoints |
### Other headers & utilities
| Middleware | Role |
| --- | --- |
| `security-headers` | `X-Content-Type-Options: nosniff`, disables legacy `X-XSS-Protection` |
| `hsts-header` | HSTS 2y, includeSubDomains, preload |
| `frame-deny-header` | `X-Frame-Options: DENY` |
| `frame-sameorigin-header` | `X-Frame-Options: SAMEORIGIN` |
| `cross-origin-opener-strict-policy` | `Cross-Origin-Opener-Policy: same-origin` |
| `gzip` | Response compression |
| `referrer-*-header` | Referrer-Policy tiers (`open``supermax`) |
| `strip-server-path` | Strips `/db`, `/server`, `/traefik` prefixes |
### Auth & access
| Middleware | Role |
| --- | --- |
| `authentik-outpost` | ForwardAuth to Authentik outpost (`outpost.mifi.holdings`) |
| `authentik-no-cache` | Disable caching on Authentik responses |
| `authentik-static-immutable` | Long-cache immutable static assets |
| `internal-ips` | IP allowlist (edit `sourceRange` for your networks) |
### Redirects
| Middleware | Role |
| --- | --- |
| `redirect-www-to-non-www` | `www.` → apex |
| `redirect-non-www-to-www` | apex → `www.` |
| `redirect-postfixadmin` | Mail settings path redirect (site-specific) |
Update Authentik outpost URL, IP allowlists, and domain-specific redirects in `middlewares.yaml` before deploying outside this environment.
## File-based routers and services
`traefik/conf.d/routers.yaml` and `services.yaml` are stubs for optional non-Docker backends. Uncomment or add entries when you need host/path rules that are not expressed as container labels.
## Operations
```bash
# Start / recreate after config or .env changes
docker compose --env-file .env up -d
# Follow logs
docker compose logs -f traefik
# Stop (keeps the ACME volume)
docker compose down
# Inspect ACME volume location (optional)
docker volume inspect traefik_traefik-acme
```
Dynamic files under `conf.d/` reload without a full restart. Changes to `traefik.yaml`, Compose labels, ports, or env vars generally need `docker compose up -d` (or a container recreate).
## Customization checklist
1. Set `.env` from `example.env` and generate `TRAEFIK_CONSOLE_AUTH`
2. Edit TLS SANs in `traefik/traefik.yaml` for your domains
3. Adjust `internal-ips` and Authentik URLs in `middlewares.yaml`
4. Ensure backends use `traefik.enable=true` and join `NETWORK_NAME`
5. Publish host ports (`8342`/`8767`) as needed for your network edge
## Security notes
- Do not commit `.env` or real API keys
- Prefer a scoped Cloudflare API token limited to DNS edit on required zones when possible
- Keep the dashboard behind strong basic auth (and consider further restricting with `internal-ips` or Authentik)
- Review CSP chains per app; start stricter and loosen only where breakage requires it
- Do not use `docker compose down -v` in production unless you mean to discard certificates