Container hardening: non-root user, app healthcheck, localhost bind default, DSN password encoding #64

Closed
opened 2026-07-18 05:31:28 +00:00 by claude-bot · 1 comment
Contributor
  • Dockerfile has no USER; run as a non-root user, chown radar_cache /
    matrix_store.
  • Add a compose healthcheck hitting /health (db already has one; CD already
    curls it — cd.yml:74-75).
  • Default the port mapping to 127.0.0.1:${APP_PORT:-8000}:8000 and document
    the reverse-proxy/override expectation (session security assumes HTTPS in
    front).
  • DATABASE_URL interpolates ${POSTGRES_PASSWORD} unencoded
    (docker-compose.yml:12); URL-encode in an entrypoint or document the
    restricted charset in .env.example — README says "any strong password",
    which breaks on @:/%.
  • Optional: enable Renovate docker:pinDigests; add conservative mem limits.

Acceptance criteria:

  • App process runs non-root; volumes writable
  • docker compose ps shows app health
  • Fresh deploy with an @-containing password connects
  • Default compose does not expose the app beyond localhost

Filed from the 2026-07-17 codebase audit (docs/.internal/report-2026-07-17.md), finding F-25.

- Dockerfile has no `USER`; run as a non-root user, chown radar_cache / matrix_store. - Add a compose healthcheck hitting `/health` (db already has one; CD already curls it — cd.yml:74-75). - Default the port mapping to `127.0.0.1:${APP_PORT:-8000}:8000` and document the reverse-proxy/override expectation (session security assumes HTTPS in front). - `DATABASE_URL` interpolates `${POSTGRES_PASSWORD}` unencoded (docker-compose.yml:12); URL-encode in an entrypoint or document the restricted charset in .env.example — README says "any strong password", which breaks on `@:/%`. - Optional: enable Renovate `docker:pinDigests`; add conservative mem limits. **Acceptance criteria:** - [ ] App process runs non-root; volumes writable - [ ] `docker compose ps` shows app health - [ ] Fresh deploy with an `@`-containing password connects - [ ] Default compose does not expose the app beyond localhost --- _Filed from the 2026-07-17 codebase audit (`docs/.internal/report-2026-07-17.md`), finding F-25._
Author
Contributor

Done in #95 (merged to main). App now runs as non-root (UID 10001) via a gosu entrypoint that chowns the mounted cache volumes and assembles DATABASE_URL from POSTGRES_* components with a URL-encoded password (explicit DATABASE_URL still wins). Added Dockerfile + compose healthchecks on /health, a loopback-by-default port bind (APP_BIND=0.0.0.0 to expose), and a configurable mem_limit (default 1g). Verified on the dev server: PID 1 = uvicorn running as UID 10001, a special-char DB password round-trips through the encoded DSN, migrations run, /health 200. Set APP_BIND=0.0.0.0 in the dev .env so the dev deploy stays reachable after the loopback default.

Done in #95 (merged to `main`). App now runs as non-root (UID 10001) via a `gosu` entrypoint that chowns the mounted cache volumes and assembles `DATABASE_URL` from `POSTGRES_*` components with a URL-encoded password (explicit `DATABASE_URL` still wins). Added Dockerfile + compose healthchecks on `/health`, a loopback-by-default port bind (`APP_BIND=0.0.0.0` to expose), and a configurable `mem_limit` (default 1g). Verified on the dev server: PID 1 = uvicorn running as UID 10001, a special-char DB password round-trips through the encoded DSN, migrations run, `/health` 200. Set `APP_BIND=0.0.0.0` in the dev `.env` so the dev deploy stays reachable after the loopback default.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/WeatherBot#64
No description provided.