Files
GearBox/.planning/phases/14-postgresql-migration/14-02-PLAN.md

8.1 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
14-postgresql-migration 02 execute 1
docker-compose.dev.yml
docker-compose.yml
Dockerfile
entrypoint.sh
true
DB-05
truths artifacts key_links
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
path provides contains
docker-compose.dev.yml Development Postgres service postgres:16-alpine
path provides contains
docker-compose.yml Production Postgres + app services postgres:16-alpine
path provides contains
Dockerfile Updated container build drizzle-pg
from to via pattern
docker-compose.yml Dockerfile app service builds from Dockerfile 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)

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

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:

#!/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

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

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