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.
4.1 KiB
4.1 KiB
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 --rollbackrestores 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"]— never0.0.0.0. Caddy runs on.35, so the port has to be reachable from the LAN; a127.0.0.1publish answers only on.13and the public URL returns 502. LAN-only publish, Caddy on.35still the sole public listener. Decision: Sam, 2026-10-26, interaction54aeb1a2. The health gate andassert_shape()indeploy.shfollow the same address (RESUME_PUBLISH_IP).loggingcapped (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.
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.shanddeploy.sh --rollbackboth work; a deliberate bad build proves rollback.https://resume.lab.audasmedia.com.auserves the site.- Container published on
192.168.20.13:8096only, reachable from.35; confirmed withss -ltn. memswap_limit == mem_limit; confirmed withdocker 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 |