227 lines
8.1 KiB
Markdown
227 lines
8.1 KiB
Markdown
---
|
|
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>
|