--- phase: 14-postgresql-migration plan: 02 type: execute wave: 1 depends_on: [] files_modified: - docker-compose.dev.yml - docker-compose.yml - Dockerfile - entrypoint.sh autonomous: true requirements: [DB-05] must_haves: truths: - "docker compose -f docker-compose.dev.yml up starts a PostgreSQL 16 instance accessible on localhost:5432" - "Production docker-compose.yml includes Postgres service with healthcheck and the app depends on it" - "Dockerfile copies drizzle-pg/ instead of drizzle/ and no longer installs native build tools for better-sqlite3" artifacts: - path: "docker-compose.dev.yml" provides: "Development Postgres service" contains: "postgres:16-alpine" - path: "docker-compose.yml" provides: "Production Postgres + app services" contains: "postgres:16-alpine" - path: "Dockerfile" provides: "Updated container build" contains: "drizzle-pg" key_links: - from: "docker-compose.yml" to: "Dockerfile" via: "app service builds from Dockerfile" pattern: "depends_on" --- Create Docker Compose configurations for local development and production with PostgreSQL 16, and update the Dockerfile for the Postgres-based app. Purpose: Provides the database infrastructure for local dev (DB-05) and production. Must exist before anyone runs the app against real Postgres. Output: docker-compose.dev.yml (new), docker-compose.yml (rewritten for Postgres), Dockerfile (updated), entrypoint.sh (updated) @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/14-postgresql-migration/14-CONTEXT.md @.planning/phases/14-postgresql-migration/14-RESEARCH.md @Dockerfile @entrypoint.sh Task 1: Create Docker Compose files for dev and production docker-compose.dev.yml, docker-compose.yml docker-compose.yml, Dockerfile, entrypoint.sh **Step 1: Create `docker-compose.dev.yml`** per D-10 and D-11: ```yaml services: postgres: image: postgres:16-alpine environment: POSTGRES_USER: gearbox POSTGRES_PASSWORD: gearbox POSTGRES_DB: gearbox ports: - "5432:5432" volumes: - pgdata-dev:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U gearbox"] interval: 5s timeout: 3s retries: 5 volumes: pgdata-dev: ``` This is a development-only file. The app itself runs locally via `bun run dev` against this Postgres instance using `DATABASE_URL=postgresql://gearbox:gearbox@localhost:5432/gearbox`. **Step 2: Rewrite `docker-compose.yml`** for production per D-10: ```yaml services: postgres: image: postgres:16-alpine environment: POSTGRES_USER: gearbox POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: gearbox volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U gearbox"] interval: 10s timeout: 5s retries: 5 app: image: gearbox:latest environment: DATABASE_URL: postgresql://gearbox:${POSTGRES_PASSWORD}@postgres:5432/gearbox GEARBOX_URL: ${GEARBOX_URL} ports: - "3000:3000" depends_on: postgres: condition: service_healthy volumes: - uploads:/app/uploads volumes: pgdata: uploads: ``` Key changes from current docker-compose.yml: - Remove any SQLite volume mounts (data/, gearbox.db references) - Add postgres service with healthcheck - App service uses DATABASE_URL env var per D-12 - App depends_on postgres with service_healthy condition - POSTGRES_PASSWORD is externalized (not hardcoded in production) grep -q "postgres:16-alpine" docker-compose.dev.yml && grep -q "postgres:16-alpine" docker-compose.yml && grep -q "POSTGRES_PASSWORD" docker-compose.yml && grep -q "DATABASE_URL" docker-compose.yml && echo "PASS" || echo "FAIL" - docker-compose.dev.yml exists and contains `image: postgres:16-alpine` - docker-compose.dev.yml contains `POSTGRES_USER: gearbox` and `POSTGRES_PASSWORD: gearbox` and `POSTGRES_DB: gearbox` - docker-compose.dev.yml contains `ports:` with `"5432:5432"` - docker-compose.dev.yml contains a healthcheck with `pg_isready -U gearbox` - docker-compose.yml contains `image: postgres:16-alpine` - docker-compose.yml contains `DATABASE_URL: postgresql://gearbox:${POSTGRES_PASSWORD}@postgres:5432/gearbox` - docker-compose.yml contains `depends_on:` with `condition: service_healthy` - docker-compose.yml does NOT contain `gearbox.db` or `DATABASE_PATH` or `sqlite` Docker Compose dev file provides local Postgres. Production compose includes Postgres with healthcheck and app service with DATABASE_URL. Task 2: Update Dockerfile and entrypoint for PostgreSQL Dockerfile, entrypoint.sh Dockerfile, entrypoint.sh **Step 1: Update `Dockerfile`:** The current Dockerfile installs `python3 make g++` for native SQLite bindings (better-sqlite3). These are no longer needed since postgres.js is pure JavaScript. ```dockerfile FROM oven/bun:1 AS deps WORKDIR /app COPY package.json bun.lock ./ RUN bun install --frozen-lockfile FROM deps AS build COPY . . RUN bun run build FROM oven/bun:1-slim AS production WORKDIR /app ENV NODE_ENV=production COPY --from=deps /app/node_modules ./node_modules COPY --from=build /app/dist/client ./dist/client COPY src/server ./src/server COPY src/db ./src/db COPY src/shared ./src/shared COPY drizzle.config.ts package.json ./ COPY drizzle-pg ./drizzle-pg COPY entrypoint.sh ./ RUN chmod +x entrypoint.sh && mkdir -p uploads EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD bun -e "fetch('http://localhost:3000/api/health').then(r=>r.ok?process.exit(0):process.exit(1)).catch(()=>process.exit(1))" ENTRYPOINT ["./entrypoint.sh"] ``` Key changes: - Remove `RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/*` from deps stage (no native bindings needed) - Change `COPY drizzle ./drizzle` to `COPY drizzle-pg ./drizzle-pg` - Remove `mkdir -p data` (no SQLite data directory needed) **Step 2: Update `entrypoint.sh`** — no changes needed (it already runs `bun run src/db/migrate.ts` which has been rewritten to use postgres-js migrator in Plan 01). Verify it still reads: ```bash #!/bin/sh set -e bun run src/db/migrate.ts exec bun run src/server/index.ts ``` grep -q "drizzle-pg" Dockerfile && ! grep -q "python3 make g++" Dockerfile && ! grep -q "COPY drizzle ./drizzle" Dockerfile && echo "PASS" || echo "FAIL" - Dockerfile contains `COPY drizzle-pg ./drizzle-pg` - Dockerfile does NOT contain `COPY drizzle ./drizzle` (the old SQLite migrations line) - Dockerfile does NOT contain `python3 make g++` or `apt-get install` - Dockerfile does NOT contain `mkdir -p data` (no SQLite data dir) - Dockerfile still contains `COPY src/db ./src/db` and `COPY src/server ./src/server` - entrypoint.sh still contains `bun run src/db/migrate.ts` Dockerfile builds without native deps, copies drizzle-pg/ migrations. Entrypoint runs postgres-js based migration on startup. - `docker compose -f docker-compose.dev.yml config` validates successfully - `docker compose config` validates the production file - `grep -r "sqlite\|better-sqlite\|bun:sqlite" Dockerfile docker-compose.yml docker-compose.dev.yml` returns NO matches Docker Compose dev file provides PostgreSQL 16 for local development. Production compose includes Postgres + app with proper dependency chain. Dockerfile is lean (no native build tools) and copies PostgreSQL migrations. After completion, create `.planning/phases/14-postgresql-migration/14-02-SUMMARY.md`