128 lines
5.8 KiB
Markdown
128 lines
5.8 KiB
Markdown
|
|
---
|
||
|
|
phase: 17-docker-deployment
|
||
|
|
plan: 02
|
||
|
|
subsystem: infra
|
||
|
|
tags: [docker, dockerfile, docker-compose, nodejs, nextjs, standalone, smtp, neon]
|
||
|
|
|
||
|
|
# Dependency graph
|
||
|
|
requires:
|
||
|
|
- phase: 17-01
|
||
|
|
provides: output standalone in next.config.ts, GET /api/health endpoint
|
||
|
|
provides:
|
||
|
|
- Dockerfile with three-stage node:20-slim linux/amd64 build
|
||
|
|
- docker-compose.yml with env_file secrets, DNS fix, named uploads volume
|
||
|
|
- .dockerignore for build context exclusions
|
||
|
|
- .env.production.example template with 11 required vars
|
||
|
|
- DEPLOYMENT.md with migration step and startup instructions
|
||
|
|
affects: [deployment, production-ops]
|
||
|
|
|
||
|
|
# Tech tracking
|
||
|
|
tech-stack:
|
||
|
|
added: []
|
||
|
|
patterns:
|
||
|
|
- "Three-stage Dockerfile: deps (npm ci --omit=dev) → builder (npm run build) → runner (node server.js)"
|
||
|
|
- "env_file secrets injection — secrets stay in .env.production on host, never baked into image"
|
||
|
|
- "SMTP DNS fix: dns array [8.8.8.8, 1.1.1.1] + NODE_OPTIONS=--dns-result-order=ipv4first"
|
||
|
|
- "Named Docker volume uploads:/app/uploads for persistent PDF storage across restarts"
|
||
|
|
|
||
|
|
key-files:
|
||
|
|
created:
|
||
|
|
- teressa-copeland-homes/Dockerfile
|
||
|
|
- teressa-copeland-homes/docker-compose.yml
|
||
|
|
- teressa-copeland-homes/.dockerignore
|
||
|
|
- teressa-copeland-homes/.env.production.example
|
||
|
|
- teressa-copeland-homes/DEPLOYMENT.md
|
||
|
|
modified:
|
||
|
|
- teressa-copeland-homes/.gitignore
|
||
|
|
|
||
|
|
key-decisions:
|
||
|
|
- "--platform=linux/amd64 on all 3 FROM lines — home server is x86_64; Mac ARM cross-compiles correctly"
|
||
|
|
- "node:20-slim (Debian) not Alpine — @napi-rs/canvas requires glibc, Alpine musl incompatible"
|
||
|
|
- "seeds/ copied into runner stage at /app/seeds — runtime dependency for form library import feature"
|
||
|
|
- "uploads/ pre-created in runner stage, owned by nextjs user — volume mount works without root"
|
||
|
|
- "HEALTHCHECK uses wget (in Debian slim by default) not curl (not installed)"
|
||
|
|
- ".env.production.example force-added past .env* gitignore glob — example template should be committed"
|
||
|
|
- "Migrations run from host, not inside container — CMD is simply node server.js per D-02"
|
||
|
|
|
||
|
|
patterns-established:
|
||
|
|
- "Docker deployment: build on host → push or deploy to server → run migrations → docker compose up"
|
||
|
|
|
||
|
|
requirements-completed: [DEPLOY-01, DEPLOY-02, DEPLOY-03, DEPLOY-04, DEPLOY-05]
|
||
|
|
|
||
|
|
# Metrics
|
||
|
|
duration: 3min
|
||
|
|
completed: 2026-04-03
|
||
|
|
---
|
||
|
|
|
||
|
|
# Phase 17 Plan 02: Docker Deployment Files Summary
|
||
|
|
|
||
|
|
**Three-stage node:20-slim Dockerfile with linux/amd64, env_file secrets injection, SMTP DNS fix, named uploads volume, and DEPLOYMENT.md documenting host-side migration workflow**
|
||
|
|
|
||
|
|
## Performance
|
||
|
|
|
||
|
|
- **Duration:** 3 min
|
||
|
|
- **Started:** 2026-04-03T22:55:35Z
|
||
|
|
- **Completed:** 2026-04-03T22:58:30Z
|
||
|
|
- **Tasks:** 3
|
||
|
|
- **Files modified:** 6
|
||
|
|
|
||
|
|
## Accomplishments
|
||
|
|
|
||
|
|
- Created three-stage `Dockerfile` using `node:20-slim` with `--platform=linux/amd64` on all 3 FROM lines; seeds/ copied for form library; HEALTHCHECK via wget on /api/health
|
||
|
|
- Created `docker-compose.yml` with runtime env_file secrets injection, SMTP DNS fix (dns array + NODE_OPTIONS), named volume `uploads:/app/uploads`, and `restart: unless-stopped`
|
||
|
|
- Created `.dockerignore` (excludes node_modules, .next, .git, .env*, uploads/, *.md), `.env.production.example` with exactly 11 required vars (no BLOB token, no dev scraping creds), and `DEPLOYMENT.md` documenting the full workflow
|
||
|
|
|
||
|
|
## Task Commits
|
||
|
|
|
||
|
|
Each task was committed atomically:
|
||
|
|
|
||
|
|
1. **Task 1: Dockerfile, .dockerignore, .env.production.example** - `e83ced5` (feat)
|
||
|
|
2. **Task 2: docker-compose.yml and .gitignore update** - `a107970` (feat)
|
||
|
|
3. **Task 3: DEPLOYMENT.md** - `72c23f8` (feat)
|
||
|
|
|
||
|
|
## Files Created/Modified
|
||
|
|
|
||
|
|
- `teressa-copeland-homes/Dockerfile` - Three-stage linux/amd64 build; seeds/ for form library; HEALTHCHECK
|
||
|
|
- `teressa-copeland-homes/docker-compose.yml` - env_file secrets, DNS fix, named uploads volume
|
||
|
|
- `teressa-copeland-homes/.dockerignore` - Excludes dev artifacts from Docker build context
|
||
|
|
- `teressa-copeland-homes/.env.production.example` - Template with 11 required runtime vars
|
||
|
|
- `teressa-copeland-homes/DEPLOYMENT.md` - Step-by-step deploy guide with migration and health check steps
|
||
|
|
- `teressa-copeland-homes/.gitignore` - Added /uploads/ entry for Docker volume path
|
||
|
|
|
||
|
|
## Decisions Made
|
||
|
|
|
||
|
|
- `.env.production.example` force-added past `.env*` gitignore glob — it's a template with no real secrets and should be committed for operator reference
|
||
|
|
- `wget` used in HEALTHCHECK (not curl) because Debian slim includes wget by default; curl is not pre-installed in node:20-slim
|
||
|
|
- `seeds/` directory is explicitly copied into the runner stage at `/app/seeds` — `src/app/api/documents/route.ts` reads from `path.join(process.cwd(), 'seeds', 'forms')` at runtime for the form library import feature; without it, importing forms returns 404
|
||
|
|
|
||
|
|
## Deviations from Plan
|
||
|
|
|
||
|
|
None - plan executed exactly as written.
|
||
|
|
|
||
|
|
## Issues Encountered
|
||
|
|
|
||
|
|
- `docker compose config --quiet` fails locally because `.env.production` does not exist on dev machine (expected — it's server-only). Verified with a temporary empty stub file; compose syntax parsed cleanly.
|
||
|
|
|
||
|
|
## User Setup Required
|
||
|
|
|
||
|
|
The operator (Teressa) must perform these steps on the server before first deploy:
|
||
|
|
|
||
|
|
1. Copy `.env.production.example` to `.env.production` and fill in all 11 values
|
||
|
|
2. Run `DATABASE_URL=<neon-url> npx drizzle-kit migrate` from the project directory
|
||
|
|
3. Run `docker compose up -d --build`
|
||
|
|
4. Verify with `curl http://localhost:3000/api/health`
|
||
|
|
|
||
|
|
See `teressa-copeland-homes/DEPLOYMENT.md` for full instructions.
|
||
|
|
|
||
|
|
## Next Phase Readiness
|
||
|
|
|
||
|
|
Phase 17 is complete. All Docker deployment infrastructure is in place:
|
||
|
|
- Plan 01: standalone output, health endpoint, pool limit, @vercel/blob removal
|
||
|
|
- Plan 02: Dockerfile, compose, ignore files, env template, deployment docs
|
||
|
|
|
||
|
|
The app is ready to be deployed to Teressa's home server via `docker compose up -d --build`.
|
||
|
|
|
||
|
|
---
|
||
|
|
*Phase: 17-docker-deployment*
|
||
|
|
*Completed: 2026-04-03*
|