Files
ai-resume/docs/phase-3-deploy.md
Sam Rolfe b267e55f8c Replace Vite/React scaffold with Go build spec
- AGENTS.md: build spec for Paperclip (constraints, conventions, 7 phases)
- docs/phase-1..7: per-phase work, DoD, out-of-scope, risks
- reference/: frozen decisions, stack+ports+budgets, content map, acceptance tests
- agents/: persona and security policy drafts
- Retires the React scaffold; history retained as record of the false start
2026-10-26 16:02:35 +11:00

103 lines
3.7 KiB
Markdown

# Phase 3 — Public deployment
**Goal:** a live URL Sam can send someone. Early, deliberately — not at the end.
**Depends on:** Phase 2.
> **Read `AGENTS.md` §4.2 first.** This phase touches a shared host. The Caddy change is **Sam's to apply**, not the agent's.
---
## Facts
| Item | Value |
|---|---|
| Public hostname | `resume.lab.audasmedia.com.au` |
| DNS | Wildcard `*.lab.audasmedia.com.au` already resolves publicly — **no DNS work needed** |
| TLS | Terminated by Caddy on `.35` |
| App port | `8096` on `.13` (verify free; 8095 belongs to `offsite.lab`) |
| Release path | `/home/sam/deployment/resume/` |
---
## Work
### 3.1 The release mechanism
```
deploy.sh
├─ build + test (fail fast)
├─ copy to releases/<timestamp>/
├─ flip `current` symlink (atomic)
├─ docker compose up -d
├─ curl http://127.0.0.1:8096/healthz → must be ready
└─ on failure: flip back to `previous`, exit non-zero
```
- `deploy.sh --rollback` restores the previous release in one command.
- No agent hand-edits files on a production host. Agents invoke the script or fix the script in git.
- Deploy is a **script**, not a model decision. `AGENTS.md` §4.6.
### 3.2 Container
- `mem_limit` = `memswap_limit` = `120m`.
- `ports: ["127.0.0.1:8096:8096"]` — never `0.0.0.0`.
- `logging` capped (`max-size: 10m`, `max-file: 3`).
- `restart: unless-stopped`.
### 3.3 Caddy — **produce the diff, Sam applies it**
Agent output is the exact block to add to `.35`'s Caddyfile, plus the reload command. Do not apply it.
```caddy
resume.lab.audasmedia.com.au {
encode zstd gzip
reverse_proxy 192.168.20.13:8096
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
X-Frame-Options "SAMEORIGIN"
Content-Security-Policy "default-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'"
}
@static path *.css *.js *.svg *.png *.jpg *.webp *.woff2
header @static Cache-Control "public, max-age=31536000, immutable"
}
```
Apply with `caddy reload` (not restart). Verify the other hostnames still resolve afterwards.
### 3.4 Repo visibility
Repo stays private through this phase. Sam decides on public visibility after the URL is verified.
---
## Definition of Done
- [ ] `deploy.sh` and `deploy.sh --rollback` both work; a deliberate bad build proves rollback.
- [ ] `https://resume.lab.audasmedia.com.au` serves the site.
- [ ] Container bound to `127.0.0.1` only; confirmed with `ss -ltn`.
- [ ] `memswap_limit == mem_limit`; confirmed with `docker inspect`.
- [ ] Security headers present; CSP does not break the page.
- [ ] A1 still passes over the public URL with the AI layer stopped.
- [ ] Sam has applied the Caddy block and confirmed the other lab hostnames still work.
- [ ] Rollback rehearsed once and documented.
---
## Out of scope
- Rate limiting for the AI endpoint (Phase 6).
- Monitoring, alerting, restore drills — add a minimal uptime check only.
- Any change to other sites, containers or the NixOS config.
---
## Risks
| Risk | Mitigation |
|---|---|
| Caddy change breaks another site | Additive block only; `caddy reload` not restart; Sam verifies the other hostnames immediately after |
| Deploying a half-finished build | `deploy.sh` health gate + rollback; the site is designed to be useful at every stage |
| Port 8096 occupied | Verify before compose; pick the next free port and record it in `reference/stack.md` |
| Host memory pressure | 120 m cap; the app is static-serving and should idle well under it. Measure and record actual RSS |