docs(14-postgresql-migration): create phase plan
This commit is contained in:
226
.planning/phases/14-postgresql-migration/14-02-PLAN.md
Normal file
226
.planning/phases/14-postgresql-migration/14-02-PLAN.md
Normal file
@@ -0,0 +1,226 @@
|
||||
---
|
||||
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>
|
||||
Reference in New Issue
Block a user