--- phase: 01-project-infrastructure-module-wiring plan: 06 subsystem: dev-ergonomics tags: [docker-compose, postgres, readme, local-dev, infra] dependency_graph: requires: [] provides: - "Local Postgres 16 dev instance matching application.conf HOCON defaults (recipe/recipe/recipe)" - "Named volume recipe-pgdata for persistence across container restarts" - "pg_isready healthcheck enabling docker compose up --wait usage" - "README 'Local development' section documenting the two-command dev loop" affects: - "server/src/main/resources/application.conf (Plan 05 — credentials match contract)" - "Phase 3 (Households + DB migrations) — depends on a working local Postgres" - "Phase 11 (homelab deployment) — separate compose config will diverge from this dev-local one" tech_stack: added: - "postgres:16 (Docker image, pinned major version)" patterns: - "Dev-local compose file committed to repo (non-secret literal creds)" - "Healthcheck via pg_isready gating sequencing" - "Named Docker volume for data persistence" key_files: created: - "docker-compose.yml" modified: - "README.md" decisions: - "Kept it single-service: postgres only. Authentik stays on homelab (CONTEXT.md D-17); Ktor server runs via Gradle on the dev host for fast iteration." - "Pinned postgres:16 (not :latest, not :15) matching D-17 scope statement." - "No version: key in compose file — modern docker compose v2 treats it as legacy and emits warnings." - "No .env file in this plan — inline POSTGRES_* is fine for single-dev + matching application.conf defaults (D-17 / PATTERNS.md recommendation)." - "Port binding 5432:5432 is dev-local; README calls it out. Phase 11 homelab compose will use a different approach." metrics: duration_seconds: 92 duration_human: "1m32s" tasks_completed: 2 files_created: 1 files_modified: 1 completed_at: "2026-04-24T16:22:48Z" --- # Phase 01 Plan 06: Dev ergonomics — docker-compose + README Local development summary Shipped `docker-compose.yml` (single postgres:16 service, named volume, healthcheck — credentials matching Plan 05's `application.conf` HOCON defaults exactly) and a "Local development" README section documenting the `docker compose up -d postgres && ./gradlew :server:run && curl /health` dev loop, while dropping the legacy `js` target docs per D-01. ## What was built ### docker-compose.yml (20 lines) - `services.postgres`: - `image: postgres:16` (pinned major version) - `container_name: recipe-postgres` - `environment`: `POSTGRES_DB / POSTGRES_USER / POSTGRES_PASSWORD` all literal `recipe` - `ports: "5432:5432"` (dev-local loopback via host Docker) - `volumes: recipe-pgdata:/var/lib/postgresql/data` (persistence) - `healthcheck`: `pg_isready -U recipe -d recipe` every 5s, timeout 5s, 5 retries - Top-level `volumes.recipe-pgdata:` (named volume declaration) - No `version:` key (modern compose v2) - No additional services (no Authentik — lives on user's homelab per D-17) ### README.md edits **Edit A — dropped js target block** (lines 77-85 of previous README): the "- for the JS target (slower, supports older browsers)" paragraph and its two command blocks were deleted. The `wasmJs` paragraph is preserved intact. **Edit B — inserted new "Local development" section** (after the iOS subsection, before the trailing `---` horizontal rule): - Two-command boot: `docker compose up -d postgres` + `./gradlew :server:run` - Smoke test: `curl http://localhost:8080/health` with expected `{"status":"ok"}` response - Documented env-var overrides: `DATABASE_URL`, `DATABASE_USER`, `DATABASE_PASSWORD`, `PORT` - Pre-commit formatter hint: `./gradlew spotlessApply` (D-10) - Full-suite: `./gradlew check` - DB reset: `docker compose down -v` (destroys `recipe-pgdata`) All other existing headings (Android, Desktop/JVM, Server, iOS, web `wasmJs`) and the top introduction (lines 1-20) are unchanged. The trailing `---` + learn-more links paragraph is unchanged. ## Credential-match contract with Plan 05 The three compose env-vars are byte-identical to the literals in `server/src/main/resources/application.conf`: | compose env | application.conf | |-------------|------------------| | `POSTGRES_DB: recipe` | JDBC URL path `/recipe` | | `POSTGRES_USER: recipe` | `user = "recipe"` | | `POSTGRES_PASSWORD: recipe` | `password = "recipe"` | Verified via `grep -c '^\s*POSTGRES_\(DB\|USER\|PASSWORD\): recipe$' docker-compose.yml` → `3`. ## Requirements addressed - **INFRA-02** — local development environment via `docker-compose.yml` and README dev loop documentation. ## Tasks executed | Task | Name | Commit | Files | |------|------|--------|-------| | 1 | Create docker-compose.yml at repo root | `af4428f` | docker-compose.yml (new) | | 2 | Add "Local development" section to README.md and drop js target docs | `f691400` | README.md (modified) | ## Deviations from Plan None — plan executed exactly as written. No Rule 1-3 auto-fixes, no checkpoints, no auth gates. Both `` verify blocks and every acceptance criterion passed on first attempt. ## Threat surface scan No new network endpoints, auth paths, file access patterns, or schema changes at trust boundaries were introduced beyond what the plan's `` already covers (T-01-06-01..04). The `5432:5432` host binding and literal `recipe/recipe/recipe` credentials are the exact surface the plan's STRIDE register dispositions (`mitigate`/`accept`) already cover. No new flags. ## Known stubs None. Both deliverables are complete — no placeholders, no TODOs, no empty data paths. ## Verification **Task 1 automated check:** ``` test -f docker-compose.yml && grep -q 'image: postgres:16' ... && grep -q 'pg_isready -U recipe -d recipe' ... && grep -q '^volumes:$' ... → VERIFY PASS grep -c '^\s*POSTGRES_\(DB\|USER\|PASSWORD\): recipe$' docker-compose.yml → 3 ``` **Task 2 automated check:** ``` grep -q 'Local development' && grep -q 'docker compose up -d postgres' && grep -q 'curl http://localhost:8080/health' && grep -q 'DATABASE_URL' && grep -q 'gradlew spotlessApply' && grep -q 'docker compose down -v' && ! grep -q 'jsBrowserDevelopmentRun' && grep -q 'wasmJsBrowserDevelopmentRun' → VERIFY PASS ``` **Acceptance criteria — Task 2 individually confirmed:** - `Local development` appears exactly once (section heading) - All 4 env-vars listed: `DATABASE_URL`, `DATABASE_USER`, `DATABASE_PASSWORD`, `PORT` - `gradlew check` present - Existing section headings (Android / Desktop (JVM) / Server / iOS) all preserved (grep `-c` → `1` each) - `jsBrowserDevelopmentRun` absent; `wasmJsBrowserDevelopmentRun` present - Top introduction (lines 1-20) unchanged ## Manual sanity checks (optional, not blocking) Skipped per plan ``: - `docker compose config` YAML parse — not blocking per plan; docker may not be running in this worktree sandbox. - `docker compose up -d postgres && pg_isready` live test — not required; will be validated in Phase 3 when migrations land. ## Notes for downstream plans - **Plan 05** (this wave) — credential contract lives in both files; any future change to the `recipe/recipe/recipe` triple MUST update both `application.conf` AND `docker-compose.yml` in the same commit. - **Phase 3** (Households + DB migrations) — can add `depends_on: { postgres: { condition: service_healthy } }` to a future `server` service in compose if we ever run the Ktor server in Docker; the healthcheck is already wired for it. - **Phase 11** (homelab deployment) — will ship a separate compose file (not editing this one) because homelab creds are secret and this file's creds are deliberately non-secret literals. ## Self-Check: PASSED - `docker-compose.yml` exists at repo root: FOUND - `README.md` contains "Local development" section: FOUND - Commit `af4428f` (Task 1): FOUND in `git log` - Commit `f691400` (Task 2): FOUND in `git log` - All acceptance criteria from both tasks verified via grep - No file deletions in either commit