# Traefik reverse proxy stack Docker Compose stack for [Traefik](https://traefik.io/) with Let’s 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 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 Let’s 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:///` - Entrypoint: `websecure` - Auth: middleware `traefik-auth` (basic auth from `TRAEFIK_CONSOLE_AUTH`) - Certificate: Let’s 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 `@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