[E00-S02-T04] DB volume persists across restart/recreate #385

Merged
kpcto merged 2 commits from feature/171 into main 2026-08-29 00:35:08 +00:00
Member

What changed

Implements the [E00-S02-T04] DB volume persistence (#171) on top of the [E00-S02-T01/T02/T03] Compose baseline (#59): the database now survives docker compose restart and docker compose down + docker compose up -d (recreate).

  • compose.yaml — the db service now mounts the named volume db-data at PostgreSQL's data directory (/var/lib/postgresql/data), and the file declares the volume in a top-level volumes: map. Named volumes survive docker compose down (which only removes containers and anonymous volumes), so the database files persist across container recreate; docker compose down -v resets the data (the issue's rollback note). Header comment updated: T04 is in scope, T05 (non-root) / T06 (read-only root fs) remain out of scope.
  • apps/server/Dockerfile — header comment doc-accuracy only: T04 is a Compose-level concern (the image itself is unchanged); T05/T06 remain later tasks.
  • tests/compose-config.test.mjs — locks in both acceptance criteria: a static assertion that the db service mounts db-data:/var/lib/postgresql/data and the top-level volumes: map declares db-data (both halves are required — removing either breaks the criterion), non-vacuous mutation probes, and a Docker-gated real-stack probe that writes a fixture row through the running db service, then asserts it is still queryable after docker compose restart (acceptance 1) and after docker compose down → docker compose up -d (acceptance 2, containers recreated from scratch — only the named volume can carry the data across). The probe cleans up with docker compose down -v so runs stay isolated.

Explicitly out of scope per the brief, not touched: app health endpoint (E00-S02-T03 — unchanged behavior), non-root execution (E00-S02-T05), read-only root filesystem (E00-S02-T06).

Criterion → test table

Acceptance criterion Test (fails without the committed state)
database volume persists across restart tests/compose-config.test.mjs — "a database fixture survives docker compose restart and recreate (named db-data volume)" (Docker-gated): after docker compose up -d + fixture row, docker compose restart restarts the containers in place and the probe asserts the fixture row is still queryable (SELECT count(*) → 1). Static: "the database volume persists across restart and recreate (db mounts the named db-data volume)" requires the db service volume mount; mutation probes removing the mount / the db-data declaration / changing the mount target all fail
database volume persists across recreate tests/compose-config.test.mjs — same real-stack probe: docker compose down (without -v, so named volumes survive) removes the containers, docker compose up -d recreates them from scratch, and the probe asserts the fixture row is still queryable — the container filesystem is discarded on recreate, so only the named db-data volume can carry the data. Static: the top-level volumes: map must declare db-data (the mount target must exist and survive recreate)

Test plan executed

  • node --test tests/compose-config.test.mjs → 17/17 pass, 3 skipped (the three Docker-gated probes — docker compose config, the up/down smoke probe, and the new persistence probe — skip cleanly where no Docker daemon exists; this sandbox has no Docker) ✓
  • node --test tests/*.test.mjs (full suite) → 63 pass / 3 skipped / 12 fail, and the identical 12 failures exist on clean main in this sandbox (frozen-install / root-commands / strict-tsconfig / node-engine suites need pnpm install + Node 24, which this environment lacks — no node_modules, Node 22.23.2 here). My branch adds +4 passing tests and +1 Docker-gated skip, zero new failures (verified via git stash baseline comparison) ✓
  • compose.yaml parses with the suite's committed YAML subset parser and passes the static + mutation probes; the real-stack probe is exactly the acceptance sequence (fixture → restart → verify → down → up → verify) and is designed to run on CI/dev machines with Docker.

Risks / notes

  • The probe hardcodes the committed default credentials (eppp/eppp), the same ones the app's DATABASE_URL already hardcodes in compose.yaml — consistent with the existing T01/T02/T03 probes.
  • Mount target /var/lib/postgresql/data is the official postgres:18-bookworm image's PGDATA (it declares VOLUME there), so the named volume replaces the image's anonymous volume and actually holds the database files.
  • Docker-gated tests skip without a daemon, so the suite stays green everywhere while giving real container-level validation where Docker exists (same pattern as T01/T02/T03).

Refs #171

## What changed Implements the [E00-S02-T04] DB volume persistence (#171) on top of the [E00-S02-T01/T02/T03] Compose baseline (#59): the database now **survives `docker compose restart` and `docker compose down` + `docker compose up -d` (recreate)**. - **`compose.yaml`** — the `db` service now mounts the named volume **`db-data`** at PostgreSQL's data directory (`/var/lib/postgresql/data`), and the file declares the volume in a top-level `volumes:` map. Named volumes survive `docker compose down` (which only removes containers and anonymous volumes), so the database files persist across container recreate; `docker compose down -v` resets the data (the issue's rollback note). Header comment updated: T04 is in scope, T05 (non-root) / T06 (read-only root fs) remain out of scope. - **`apps/server/Dockerfile`** — header comment doc-accuracy only: T04 is a Compose-level concern (the image itself is unchanged); T05/T06 remain later tasks. - **`tests/compose-config.test.mjs`** — locks in both acceptance criteria: a static assertion that the `db` service mounts `db-data:/var/lib/postgresql/data` **and** the top-level `volumes:` map declares `db-data` (both halves are required — removing either breaks the criterion), non-vacuous mutation probes, and a Docker-gated **real-stack probe** that writes a fixture row through the running `db` service, then asserts it is still queryable after `docker compose restart` (acceptance 1) and after `docker compose down` → `docker compose up -d` (acceptance 2, containers recreated from scratch — only the named volume can carry the data across). The probe cleans up with `docker compose down -v` so runs stay isolated. Explicitly out of scope per the brief, **not touched**: app health endpoint (E00-S02-T03 — unchanged behavior), non-root execution (E00-S02-T05), read-only root filesystem (E00-S02-T06). ## Criterion → test table | Acceptance criterion | Test (fails without the committed state) | | --- | --- | | database volume persists across restart | `tests/compose-config.test.mjs` — **"a database fixture survives docker compose restart and recreate (named db-data volume)"** (Docker-gated): after `docker compose up -d` + fixture row, `docker compose restart` restarts the containers in place and the probe asserts the fixture row is still queryable (`SELECT count(*)` → 1). Static: "the database volume persists across restart and recreate (db mounts the named db-data volume)" requires the `db` service volume mount; mutation probes removing the mount / the `db-data` declaration / changing the mount target all fail | | database volume persists across recreate | `tests/compose-config.test.mjs` — same real-stack probe: `docker compose down` (without `-v`, so named volumes survive) removes the containers, `docker compose up -d` recreates them from scratch, and the probe asserts the fixture row is still queryable — the container filesystem is discarded on recreate, so only the named `db-data` volume can carry the data. Static: the top-level `volumes:` map must declare `db-data` (the mount target must exist and survive recreate) | ## Test plan executed - `node --test tests/compose-config.test.mjs` → **17/17 pass, 3 skipped** (the three Docker-gated probes — `docker compose config`, the up/down smoke probe, and the new persistence probe — skip cleanly where no Docker daemon exists; this sandbox has no Docker) ✓ - `node --test tests/*.test.mjs` (full suite) → 63 pass / 3 skipped / 12 fail, and the **identical 12 failures exist on clean `main`** in this sandbox (frozen-install / root-commands / strict-tsconfig / node-engine suites need `pnpm install` + Node 24, which this environment lacks — no `node_modules`, Node 22.23.2 here). My branch adds **+4 passing tests and +1 Docker-gated skip, zero new failures** (verified via `git stash` baseline comparison) ✓ - `compose.yaml` parses with the suite's committed YAML subset parser and passes the static + mutation probes; the real-stack probe is exactly the acceptance sequence (fixture → restart → verify → down → up → verify) and is designed to run on CI/dev machines with Docker. ## Risks / notes - The probe hardcodes the committed default credentials (`eppp`/`eppp`), the same ones the app's `DATABASE_URL` already hardcodes in `compose.yaml` — consistent with the existing T01/T02/T03 probes. - Mount target `/var/lib/postgresql/data` is the official `postgres:18-bookworm` image's `PGDATA` (it declares `VOLUME` there), so the named volume replaces the image's anonymous volume and actually holds the database files. - Docker-gated tests `skip` without a daemon, so the suite stays green everywhere while giving real container-level validation where Docker exists (same pattern as T01/T02/T03). Refs #171
bot-implementer added 2 commits 2026-08-29 00:32:29 +00:00
Mount the named `db-data` volume at PostgreSQL's data directory
(/var/lib/postgresql/data) on the db service and declare it in the
top-level volumes map, so the database survives `docker compose
restart` and `docker compose down` + `up -d` (recreate). Reset with
`docker compose down -v` per the issue rollback note. Header comments
in compose.yaml and the server Dockerfile updated: T04 is no longer out
of scope; T05/T06 remain.
test: lock in DB volume persistence criteria (E00-S02-T04)
CI / Frozen lockfile install (pull_request) Successful in 45s
b51af02d06
compose-config: assert the db service mounts the named db-data volume at
the PostgreSQL data directory and the top-level volumes map declares it;
non-vacuous mutation probes (missing mount, missing volume declaration,
wrong mount target all fail); Docker-gated real-stack probe writes a
fixture row and asserts it survives `docker compose restart` (restart)
and `docker compose down` + `up -d` (recreate), cleaned up with
`docker compose down -v`.
kpcto merged commit a634168d9c into main 2026-08-29 00:35:08 +00:00
kpcto deleted branch feature/171 2026-08-29 00:35:09 +00:00
Sign in to join this conversation.