16 KiB
docker compose up --wait block until ready"
- "README.md has a 'Local development' section documenting the full dev loop (docker compose up, gradlew server:run, curl /health, gradlew spotlessApply)"
- "README.md no longer documents the dropped js target (D-01); wasmJs section is preserved"
artifacts:
- path: "docker-compose.yml"
provides: "postgres:16 service on port 5432 with named volume and healthcheck"
contains: "image: postgres:16", "POSTGRES_DB: recipe", "recipe-pgdata"
- path: "README.md"
provides: "Updated dev docs with Local development section, no js target docs"
contains: "Local development", "docker compose up -d postgres"
key_links:
- from: "docker-compose.yml"
to: "server/src/main/resources/application.conf"
via: "POSTGRES_DB=recipe / POSTGRES_USER=recipe / POSTGRES_PASSWORD=recipe defaults match HOCON localhost URL"
pattern: "POSTGRES_(DB|USER|PASSWORD):\s*recipe"
- from: "README.md Local development section"
to: "server/src/main/kotlin/dev/ulfrx/recipe/Application.kt"
via: "curl http://localhost:8080/health"
pattern: "curl .+ /health"
Deliver the local developer ergonomics promised by D-17: a `docker-compose.yml` at the repo root running `postgres:16` with credentials + volume + healthcheck that align exactly with Plan 05's `application.conf` HOCON defaults, plus a "Local development" section in `README.md` documenting the dev loop. Drop the legacy `js` target documentation from `README.md` (D-01).
Purpose: Phase 3 (Households / DB migrations) and Phase 11 (homelab deploy) both assume a working local Postgres is one command away. This plan closes that gap so docker compose up -d postgres && ./gradlew :server:run is a two-command dev loop. Authentik is NOT in this compose file — it lives on the user's homelab (CONTEXT.md D-17).
Output: 1 new YAML file, 1 README edit. Entirely independent of Plans 01-05 in terms of files_modified — runs safely in parallel.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/01-project-infrastructure-module-wiring/01-CONTEXT.md @.planning/phases/01-project-infrastructure-module-wiring/01-RESEARCH.md @.planning/phases/01-project-infrastructure-module-wiring/01-PATTERNS.md @.planning/phases/01-project-infrastructure-module-wiring/01-VALIDATION.md @README.md @CLAUDE.mdFrom server/src/main/resources/application.conf (Plan 05 created — value match required):
database {
url = "jdbc:postgresql://localhost:5432/recipe"
url = ${?DATABASE_URL}
user = "recipe"
user = ${?DATABASE_USER}
password = "recipe"
password = ${?DATABASE_PASSWORD}
}
So docker-compose.yml MUST use:
POSTGRES_DB: recipe(matches/recipein jdbc URL path)POSTGRES_USER: recipePOSTGRES_PASSWORD: recipe- port
5432:5432(matches URL port)
From README.md current content:
- Section "Build and Run Web Application" (lines 63-85) documents BOTH
wasmJsBrowserDevelopmentRunANDjsBrowserDevelopmentRun— thejspart must go per D-01. - "Build and Run Android/Desktop/Server/iOS" sections are fine and stay.
services:
postgres:
image: postgres:16
container_name: recipe-postgres
environment:
POSTGRES_DB: recipe
POSTGRES_USER: recipe
POSTGRES_PASSWORD: recipe
ports:
- "5432:5432"
volumes:
- recipe-pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U recipe -d recipe"]
interval: 5s
timeout: 5s
retries: 5
volumes:
recipe-pgdata:
CRITICAL:
image: postgres:16— pinned major version (D-17 specifiespostgres:16).POSTGRES_DB: recipe,POSTGRES_USER: recipe,POSTGRES_PASSWORD: recipe— all MUST equal"recipe"(matchesapplication.confHOCON defaults from Plan 05 —jdbc:postgresql://localhost:5432/recipe, userrecipe, passwordrecipe).- Named volume
recipe-pgdata— survives container restart. Drop withdocker compose down -vif you need a fresh DB. - Healthcheck uses
pg_isready -U recipe -d recipesodocker compose up --wait postgresordepends_on: { postgres: { condition: service_healthy } }works (Phase 3+ may add this). - Port
5432:5432— binds host port 5432 to container port 5432. Document in README that this is dev-local only. - Do NOT add any other service (no Authentik — lives on user's homelab per D-17; no server — Ktor runs via Gradle on host for dev iteration).
- No
.envfile — D-17 / PATTERNS.md "Recommendation on.envvs inline": inline is fine for single-dev + matching application.conf defaults.
The file has NO leading version key (version: "3" etc. is legacy Docker Compose syntax — unnecessary in modern docker compose v2, and omitting it avoids a warning).
test -f docker-compose.yml && grep -q 'image: postgres:16' docker-compose.yml && grep -q 'POSTGRES_DB: recipe' docker-compose.yml && grep -q 'POSTGRES_USER: recipe' docker-compose.yml && grep -q 'POSTGRES_PASSWORD: recipe' docker-compose.yml && grep -q 'recipe-pgdata:/var/lib/postgresql/data' docker-compose.yml && grep -q '"5432:5432"' docker-compose.yml && grep -q 'pg_isready -U recipe -d recipe' docker-compose.yml && grep -q '^volumes:$' docker-compose.yml && grep -q ' recipe-pgdata:' docker-compose.yml
<acceptance_criteria>
- docker-compose.yml exists at repo root (test -f docker-compose.yml)
- docker-compose.yml contains image: postgres:16 (not postgres:latest, not postgres:15, not postgres)
- docker-compose.yml contains container_name: recipe-postgres
- docker-compose.yml has POSTGRES_DB: recipe, POSTGRES_USER: recipe, POSTGRES_PASSWORD: recipe — all exactly recipe (lowercase, no variation)
- docker-compose.yml has port mapping "5432:5432"
- docker-compose.yml declares volume recipe-pgdata in both the service volumes: section AND the top-level volumes: section
- docker-compose.yml has a healthcheck: block using pg_isready -U recipe -d recipe
- docker-compose.yml does NOT contain a version: key (modern compose v2)
- docker-compose.yml does NOT define any service other than postgres (D-17: Authentik stays on homelab)
- Credentials match Plan 05's application.conf defaults (cross-check: grep 'user = "recipe"' server/src/main/resources/application.conf and grep 'password = "recipe"' server/src/main/resources/application.conf both return 1 line each — if Plan 05 is not complete, skip this sub-check)
</acceptance_criteria>
docker-compose.yml ships postgres:16 matching application.conf defaults; single-service compose file.
Edit A: Drop the js target section — delete lines 77-85 of the current README (the "- for the JS target (slower, supports older browsers): - on macOS/Linux ... ./gradlew :composeApp:jsBrowserDevelopmentRun - on Windows ..." block). Keep lines 68-76 (the wasmJs block). The entire "Build and Run Web Application" subsection should retain ONLY the wasmJs paragraph.
Resulting "Build and Run Web Application" subsection:
### Build and Run Web Application
To build and run the development version of the web app, use the run configuration from the run widget
in your IDE's toolbar or run it directly from the terminal:
- for the Wasm target (faster, modern browsers):
- on macOS/Linux
```shell
./gradlew :composeApp:wasmJsBrowserDevelopmentRun
```
- on Windows
```shell
.\gradlew.bat :composeApp:wasmJsBrowserDevelopmentRun
```
Edit B: Insert a new "Local development" section AFTER the "Build and Run iOS Application" subsection and BEFORE the trailing --- horizontal rule (around line 92 in the current file). The new section:
### Local development
The server requires Postgres. A `docker-compose.yml` at the repo root ships a local Postgres
instance whose credentials match `application.conf` defaults (`recipe`/`recipe`/`recipe`).
Boot the database and server:
```shell
docker compose up -d postgres
./gradlew :server:run
Verify the server is up:
curl http://localhost:8080/health
# expected: {"status":"ok"}
Environment overrides (optional — set any of these to override application.conf defaults):
DATABASE_URL— JDBC URL (defaultjdbc:postgresql://localhost:5432/recipe)DATABASE_USER— DB user (defaultrecipe)DATABASE_PASSWORD— DB password (defaultrecipe)PORT— Ktor port (default8080)
Before committing, format all Kotlin + Gradle + Markdown files:
./gradlew spotlessApply
The full check (Spotless + all tests across all targets):
./gradlew check
Reset the local database (destroys the recipe-pgdata volume):
docker compose down -v
Do NOT modify:
- The top-level introduction (lines 1-20)
- The "Build and Run Android Application" section
- The "Build and Run Desktop (JVM) Application" section
- The "Build and Run Server" section
- The "Build and Run iOS Application" section
- The trailing `---` + the learn-more links + the Compose/Wasm feedback paragraph
Keep the existing markdown heading level (`###`) for the new "Local development" section — matches the surrounding siblings.
</action>
<verify>
<automated>grep -q 'Local development' README.md && grep -q 'docker compose up -d postgres' README.md && grep -q 'curl http://localhost:8080/health' README.md && grep -q 'DATABASE_URL' README.md && grep -q 'gradlew spotlessApply' README.md && grep -q 'docker compose down -v' README.md && ! grep -q 'jsBrowserDevelopmentRun' README.md && grep -q 'wasmJsBrowserDevelopmentRun' README.md</automated>
</verify>
<acceptance_criteria>
- `README.md` contains the string `Local development` exactly once (new section heading)
- `README.md` contains `docker compose up -d postgres` as a documented command
- `README.md` contains `curl http://localhost:8080/health` as a documented command
- `README.md` lists all 4 env-var overrides: `DATABASE_URL`, `DATABASE_USER`, `DATABASE_PASSWORD`, `PORT`
- `README.md` contains `gradlew spotlessApply` (pre-commit formatter hint per D-10)
- `README.md` contains `gradlew check` (full-suite command)
- `README.md` contains `docker compose down -v` (volume reset hint)
- `README.md` does NOT contain `jsBrowserDevelopmentRun` (D-01 — js target dropped)
- `README.md` STILL contains `wasmJsBrowserDevelopmentRun` (wasmJs kept per D-01)
- All existing section headings ("Build and Run Android Application", "Build and Run Desktop (JVM) Application", "Build and Run Server", "Build and Run iOS Application") are preserved (unchanged)
- Top-of-file introduction (lines 1-20) is unchanged
</acceptance_criteria>
<done>README.md documents the dev loop (docker + gradle + curl + spotless + reset); legacy js target docs removed.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| Developer host → localhost:5432 Postgres | Dev-local; `docker-compose.yml` binds port on loopback via host mapping. Non-localhost access requires the developer's host to be reachable from outside the machine AND port 5432 firewall-open — normally not the case on a laptop. |
| `docker-compose.yml` (committed to git) → POSTGRES_PASSWORD=recipe | Password is literal `recipe` — non-secret by design. Real homelab creds never land in this file; homelab has its own compose file or `.env` per Phase 11. |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-01-06-01 | Information Disclosure | Postgres port 5432 exposed on `0.0.0.0` | mitigate | Host-firewall is the developer's responsibility; the literal `"5432:5432"` mapping is Docker-default (binds to all host interfaces unless the host Docker is configured otherwise). README Local development section mentions "dev-local" usage but does NOT open a CVE window — this is standard dev practice. Phase 11 (homelab) uses a different compose file that does NOT expose the port publicly. |
| T-01-06-02 | Information Disclosure | Committing real secrets to `docker-compose.yml` | mitigate | Only the literal `recipe/recipe/recipe` triple is in the file. Real homelab Postgres creds stay out of this compose file (Phase 11 will add a separate file or switch to env-var-driven compose). |
| T-01-06-03 | Tampering | `docker compose down -v` accidentally destroying valuable data | accept | Dev-only volume (`recipe-pgdata`). If Phase 3+ develops real seed data, a developer running `down -v` repopulates from migrations — zero-trust default. |
| T-01-06-04 | Denial of Service | `postgres:16` image unavailable from Docker Hub | accept | `docker pull postgres:16` is a standard image; outage would be transient and outside our control. Pinning to major version (not `:latest`) limits drift. |
</threat_model>
<verification>
Phase-level verification for this plan:
- Task 1 + Task 2 `<automated>` blocks pass.
- `tools/verify-no-version-literals.sh` continues to exit 0 (no `.gradle.kts` files modified in this plan).
- No `./gradlew` invocations — docker-compose + README are pure dev-ergonomics.
Manual sanity check (optional, NOT blocking):
- `docker compose config` parses the YAML without warnings.
- `docker compose up -d postgres && sleep 3 && docker exec recipe-postgres pg_isready -U recipe -d recipe` returns "accepting connections".
- `docker compose down` — cleans up afterward.
</verification>
<success_criteria>
- `docker-compose.yml` exists at repo root with a single `postgres:16` service + named volume + healthcheck
- Credentials in `docker-compose.yml` match `application.conf` defaults exactly (`recipe/recipe/recipe`)
- `README.md` has a new "Local development" section
- `README.md` no longer documents the `js` target
- `README.md` still documents `wasmJs` target
</success_criteria>
<output>
After completion, create `.planning/phases/01-project-infrastructure-module-wiring/01-06-SUMMARY.md` recording: docker-compose content summary (one service, one volume), credential match with Plan 05, README sections added/removed, and any deviation from D-17 (expected: none).
</output>