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