Refs #1
## Security
* Remove the CrowdSec LAPI key from config.yml. Traefik does not interpolate
env vars in its YAML config, so the key had to be literal there - which is
why the file kept drifting out of version control. It is now defined as a
docker-compose label, where compose substitutes ${CROWDSEC_LAPI_KEY} from
.env. config.yml is therefore secret-free and stays in git.
NOTE: this renames the middleware crowdsec-bouncer@file -> @docker.
Site stacks referencing @file must be updated.
The old key remains in this repo's history and must still be rotated and
scrubbed - removing it here does not un-expose it.
* Move the dashboard basic-auth hash out of docker-compose.yml into .env.
In .env every '$' must be doubled to '$$', or compose silently collapses
the hash to an empty string and the dashboard rejects every password.
* Stop publishing port 8080. The API is not insecure-mode, so nothing served
there; publishing it only widened exposure.
## Geoblock (the availability fix)
* allowUnknownCountries: false -> true. Previously a geojs.io outage made
every lookup "unknown" and therefore blocked, taking sites down for US
visitors too - a third-party service in the critical path with no degraded
mode. Now an outage degrades to "geoblocking ineffective". CrowdSec still
blocks actual attackers.
* cacheSize: 25 -> 5000. At 25, nearly every visitor triggered a fresh API
call, adding up to 750ms and risking rate limits.
## Dev host support
* Add traefik.dev.yml, selected by TRAEFIK_STATIC_CONFIG in .env (defaults to
traefik.yml, so prod behaviour is unchanged). It differs from traefik.yml
ONLY in the ACME challenge type: public port 80 forwards to prod for its
renewals, so an HTTP-01 challenge for a dev hostname hits prod's Traefik,
matches no router, and 404s. Dev uses DNS-01 via Route 53 instead, which
needs no inbound connectivity.
* Add .env.example documenting every variable.
## Captured from prod's working tree
Prod had uncommitted drift; these were running but never committed:
plugin bumps (bouncer v1.3.3->v1.3.5, geoblock v0.2.7->v0.2.8), the
entryPoints HTTP->HTTPS redirect with priority 1, and httpClientTimeout: 0.
The priority: 1 on that redirect is load-bearing - it keeps the redirect
below normal routers so basil's Host(`localhost`) HTTP route still serves.
Do not raise it.
## Verified on dev (prkl10), Traefik 3.7.10
7 routers / 8 middlewares, 0 warnings, 0 errors. Certificates issued via
DNS-01. pihole and technitium routers preserved. Sites serving 200.
Configuration changes:
- Add GeoBlock plugin (PascalMinder v0.2.7)
- Configure whitelist mode for US-only access
- Apply GeoIP middleware to all public services
- Block all non-US countries automatically
Security enhancements:
- Mealie: US access only
- Gogs: US access only
- Automatic country detection via geojs.io API
- Caching for performance (25 entries)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>