Files
ai-resume/docs/phase-3-deploy.md
Sam Rolfe df864cc29e deploy: health gate and shape assertion follow the LAN publish address
Once the port is published on 192.168.20.13 the host no longer listens on
127.0.0.1:8096, so wait_healthy(), do_status() and the rollback path would all
get connection-refused and every deploy would roll back. They now probe
$PUBLISH_IP (RESUME_PUBLISH_IP, default 192.168.20.13). assert_shape() asserts
the published host IP is that address instead of loopback; the mem_limit ==
memswap_limit checks are unchanged. check_port stays address-agnostic on
`sport = :8096`.

Records the amendment in reference/stack.md (ports table + the bind rule) and
docs/phase-3-deploy.md (§3.2 bullet, flow diagram, DoD line), citing Sam's
decision of 2026-10-26, interaction 54aeb1a2. AGENTS.md is Sam's to change and
is untouched.
2026-10-26 22:40:22 +11:00

108 lines
4.1 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://192.168.20.13: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: ["192.168.20.13:8096:8096"]` — **never** `0.0.0.0`.
Caddy runs on `.35`, so the port has to be reachable from the LAN; a
`127.0.0.1` publish answers only on `.13` and the public URL returns 502.
LAN-only publish, Caddy on `.35` still the sole public listener. Decision:
Sam, 2026-10-26, interaction `54aeb1a2`. The health gate and `assert_shape()`
in `deploy.sh` follow the same address (`RESUME_PUBLISH_IP`).
- `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 published on `192.168.20.13:8096` only, reachable from `.35`; 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 |