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

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 --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.

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