diff --git a/.planning/phases/17-docker-deployment/.gitkeep b/.planning/phases/17-docker-deployment/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/.planning/phases/17-docker-deployment/17-CONTEXT.md b/.planning/phases/17-docker-deployment/17-CONTEXT.md
new file mode 100644
index 0000000..003528a
--- /dev/null
+++ b/.planning/phases/17-docker-deployment/17-CONTEXT.md
@@ -0,0 +1,151 @@
+# Phase 17: Docker Deployment - Context
+
+**Gathered:** 2026-04-03
+**Status:** Ready for planning
+
+
+## Phase Boundary
+
+Infrastructure-only phase. No new features, no schema changes, no UI changes. Deliverables:
+1. `Dockerfile` — three-stage, `node:20-slim`, `--platform linux/amd64`
+2. `docker-compose.yml` — named uploads volume, `env_file`, SMTP DNS fix
+3. `.dockerignore` — exclude dev files from image
+4. `next.config.ts` — add `output: 'standalone'`
+5. `/api/health` route — returns 200 + DB ping
+6. `src/lib/db/index.ts` — add Neon pool limit (`max: 5`)
+7. `package.json` — remove dead `@vercel/blob` dependency
+8. `.env.production.example` — template for production env vars (no real secrets)
+9. Deployment notes (brief README or DEPLOYMENT.md) — document migration step
+
+
+
+
+## Implementation Decisions
+
+### CPU Architecture
+
+- **D-01:** All Dockerfile `FROM` lines use `--platform linux/amd64`. The home server is x86_64. Mac (ARM) builds correctly cross-compile with this flag. Applies to all three stages (deps, builder, runner).
+
+### Migration Strategy
+
+- **D-02:** Database migrations are run from the developer's machine before deploying the container — **not** in the container entrypoint or CMD. The container CMD is simply `node server.js` (or `node .next/standalone/server.js`). A brief deployment note documents: "Before starting container, run `DATABASE_URL=... npx drizzle-kit migrate` from the project directory."
+
+### Production Env Vars
+
+- **D-03:** `.env.production.example` includes ONLY the vars needed for the running app. Skyslope/URE credentials (`SKYSLOPE_LAST_NAME`, `SKYSLOPE_NRDS_ID`, `URE_USERNAME`, `URE_PASSWORD`) are excluded — they are dev-only forms scraping tools, not needed at runtime. `BLOB_READ_WRITE_TOKEN` is also excluded (dead dependency being removed).
+
+ **Required production vars:**
+ ```
+ DATABASE_URL=
+ SIGNING_JWT_SECRET=
+ AUTH_SECRET=
+ AGENT_EMAIL=
+ AGENT_PASSWORD=
+ CONTACT_EMAIL_USER=
+ CONTACT_EMAIL_PASS=
+ CONTACT_SMTP_HOST=
+ CONTACT_SMTP_PORT=
+ OPENAI_API_KEY=
+ APP_BASE_URL=
+ ```
+
+### Dockerfile Structure
+
+- **D-04:** Three-stage build pattern:
+ 1. **deps** (`--platform linux/amd64 node:20-slim AS deps`) — install production dependencies only (`npm ci --omit=dev`)
+ 2. **builder** (`--platform linux/amd64 node:20-slim AS builder`) — copy source + deps, run `npm run build` with `output: 'standalone'`
+ 3. **runner** (`--platform linux/amd64 node:20-slim AS runner`) — copy `.next/standalone`, `.next/static`, `public`, set non-root user, `CMD ["node", "server.js"]`
+
+- **D-05:** `output: 'standalone'` added to `next.config.ts`. Keeps existing `transpilePackages` and `serverExternalPackages` config intact.
+
+### Docker Compose
+
+- **D-06:** `docker-compose.yml` service definition:
+ - `env_file: .env.production` (NOT `environment:` key — secrets stay out of compose file)
+ - Named volume `uploads:/app/uploads` (persistent across container restarts)
+ - `dns: ["8.8.8.8", "1.1.1.1"]` (SMTP DNS fix — prevents `EAI_AGAIN` errors on external SMTP)
+ - `environment: NODE_OPTIONS: --dns-result-order=ipv4first` (companion to DNS fix)
+ - Port mapping `3000:3000`
+ - `restart: unless-stopped`
+
+### SMTP DNS Fix
+
+- **D-07:** Docker's internal DNS resolver intermittently fails on external hostnames (`EAI_AGAIN`). Fix is already documented — add `dns` array and `NODE_OPTIONS` to the app service. No code changes needed in the mailer itself.
+
+### Neon Connection Pool
+
+- **D-08:** `src/lib/db/index.ts` currently calls `postgres(url)` with no `max` parameter (default 10 connections). Change to `postgres(url, { max: 5 })` to leave headroom for migrations and overlapping deploys on Neon's free tier.
+
+### @vercel/blob Removal
+
+- **D-09:** Remove `@vercel/blob` from `package.json` dependencies. Research confirmed it is imported nowhere in the codebase — dead dependency. `BLOB_READ_WRITE_TOKEN` excluded from `.env.production.example` (D-03).
+
+### Health Endpoint
+
+- **D-10:** New App Router route `GET /api/health` returns `{ ok: true, db: 'connected' }` with status 200 when the database is reachable. Runs a lightweight `SELECT 1` via Drizzle. Returns `{ ok: false }` with status 503 if DB unreachable. No auth required.
+
+### Claude's Discretion
+- Exact `.dockerignore` entries (exclude `node_modules`, `.next`, `.git`, `uploads`, `.env*`, `*.md` docs, etc.)
+- Whether DEPLOYMENT.md is a separate file or a section in the existing README
+- Health check `HEALTHCHECK` directive in Dockerfile (optional Docker-native health check)
+
+
+
+
+## Canonical References
+
+**Downstream agents MUST read these before planning or implementing.**
+
+### Files Being Modified / Created
+- `teressa-copeland-homes/next.config.ts` — add `output: 'standalone'`
+- `teressa-copeland-homes/src/lib/db/index.ts` — add pool `max: 5`
+- `teressa-copeland-homes/package.json` — remove `@vercel/blob`
+- New: `teressa-copeland-homes/Dockerfile`
+- New: `teressa-copeland-homes/docker-compose.yml`
+- New: `teressa-copeland-homes/.dockerignore`
+- New: `teressa-copeland-homes/src/app/api/health/route.ts`
+- New: `teressa-copeland-homes/.env.production.example`
+
+### Research
+- `.planning/research/ARCHITECTURE.md` — Docker Compose patterns, standalone output, Neon pool
+- `.planning/research/PITFALLS.md` — Alpine incompatibility, NEXT_PUBLIC_BASE_URL build-time baking, `@vercel/blob` dead dep, DNS issues
+
+### Schema (DB connection)
+- `teressa-copeland-homes/src/lib/db/index.ts` — current db client setup
+
+
+
+
+## Existing Code Insights
+
+### Current State
+- `next.config.ts` — has `transpilePackages` and `serverExternalPackages`; no `output` field yet
+- No `Dockerfile`, no `docker-compose.yml`, no `.dockerignore` — all new files
+- `.env.local` exists (dev only); no `.env.production` — agent creates `.env.production.example`
+- `src/lib/db/index.ts` — uses `postgres(url)` (no pool limit)
+- `@vercel/blob` in `package.json` but imported nowhere
+
+### Key Constraint
+- `@napi-rs/canvas` declared as `serverExternalPackages` in next.config.ts — this confirms it's a native binary. Must use `node:20-slim` (Debian glibc), NOT Alpine. `--platform linux/amd64` ensures correct binary architecture.
+
+
+
+
+## Specific Ideas
+
+- The uploads directory is currently `path.join(process.cwd(), 'uploads')` in the app code. In standalone mode, `process.cwd()` points to `.next/standalone/`. The Docker volume should mount at `/app/.next/standalone/uploads` OR the code should reference an absolute path via an env var. The planner should check this and use the correct mount path.
+- `.env.production` (the actual secrets file) is created by Teressa on the server — it is never committed. Only `.env.production.example` is committed.
+
+
+
+
+## Deferred Ideas
+
+None — discussion stayed within phase scope.
+
+
+
+---
+
+*Phase: 17-docker-deployment*
+*Context gathered: 2026-04-03*