- Dockerfile: npm ci (reproducible, lock-pinned, matches CI) instead of npm
install; add a HEALTHCHECK against the /api/health liveness route.
- package.json: pin engines node>=22.18.0 — the server require()s .mts/.cts and
relies on Node's default TS type-stripping (unflagged only on >=22.18), which
was previously undocumented on a floating base image.
- .dockerignore: exclude .env / .env.* so a stray env file can't be baked into
the image by COPY . .
- docker-compose: set TRUST_PROXY=true (behind the reverse proxy, so Secure
cookies + client IP for rate-limit/audit are correct); flip CSRF_HTTP_ONLY to
true (SPA reads the token from an endpoint, so no JS cookie access needed);
remove the active default INIT_ADMIN_PASS; document TOKEN_ENCRYPTION_KEY (set
it to move at-rest secrets off the DB-resident key); drop obsolete version key.
Not included: TOKEN_ENCRYPTION_KEY itself — that's a secret to generate and
inject into the deployment env (the app self-migrates to the env-key scheme on
next boot); documented in compose + .env.example.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dockerfile was pinned to node:18-alpine which can't load crypto
synchronously in @rollup/plugin-terser -> serialize-javascript.
Upgrading build stage to node:22-alpine to match host Node.