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

3.7 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://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.

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