Networking: the superadmin console now mirrors `app` instead of a
loopback-only host port. Base compose drops the host port; the dev
override binds 127.0.0.1:8091; the prod overlay joins the `intranet`
network and listens on :80 with --proxy-headers so NPM can proxy it.
CLI: add `purge-test-users` (dry-run by default, --commit to delete,
--keep allow-list defaulting to the real accounts). Deletes child rows
explicitly (DB-agnostic) plus email-keyed OTPs, so smoke-test signups
that were pointed at prod can be cleaned repeatably instead of via
ad-hoc SQL. Covered by 6 new tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Compose merges list-typed fields like \`ports\` by concatenation, so the
previous prod overlay couldn't clear the base file's host port binding;
the VPS app ended up listening on both port 80 (intranet) AND host port
8800 simultaneously.
Restructured to the conventional dev/prod split:
- docker-compose.yml: no host port — prod-ready by default
- docker-compose.override.yml: dev-only host port binding (auto-loaded
by \`docker compose up\` locally, skipped when prod uses explicit -f)
- docker-compose.prod.yml: command-port 80 + intranet network only
Production invocation:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>