---
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"
---

<objective>
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)
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.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
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create Docker Compose files for dev and production</name>
  <files>docker-compose.dev.yml, docker-compose.yml</files>
  <read_first>docker-compose.yml, Dockerfile, entrypoint.sh</read_first>
  <action>
**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)
  </action>
  <verify>
    <automated>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"</automated>
  </verify>
  <acceptance_criteria>
    - 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`
  </acceptance_criteria>
  <done>Docker Compose dev file provides local Postgres. Production compose includes Postgres with healthcheck and app service with DATABASE_URL.</done>
</task>

<task type="auto">
  <name>Task 2: Update Dockerfile and entrypoint for PostgreSQL</name>
  <files>Dockerfile, entrypoint.sh</files>
  <read_first>Dockerfile, entrypoint.sh</read_first>
  <action>
**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
```
  </action>
  <verify>
    <automated>grep -q "drizzle-pg" Dockerfile && ! grep -q "python3 make g++" Dockerfile && ! grep -q "COPY drizzle ./drizzle" Dockerfile && echo "PASS" || echo "FAIL"</automated>
  </verify>
  <acceptance_criteria>
    - 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`
  </acceptance_criteria>
  <done>Dockerfile builds without native deps, copies drizzle-pg/ migrations. Entrypoint runs postgres-js based migration on startup.</done>
</task>

</tasks>

<verification>
- `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
</verification>

<success_criteria>
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.
</success_criteria>

<output>
After completion, create `.planning/phases/14-postgresql-migration/14-02-SUMMARY.md`
</output>
