Sam answered the bind question on AUD-133 (2026-10-26):
"A - dual bind: 127.0.0.1:8096 and 192.168.20.13:8096".
- docker-compose: publish 127.0.0.1 and ${APP_LAN_IP:-192.168.20.13}, never 0.0.0.0
- deploy.sh: carry APP_LAN_IP into the release .env, --lan-ip flag, health gate
now requires /healthz 200 on loopback AND the LAN address
- rehearse.sh: assert both binds, no wildcard, LAN reachability; KEEP_REHEARSAL
now really keeps the container/image; N3 waits up to 20s instead of 3s
- docs/phase-3-evidence.md: decision recorded, new measured output
DoD: 1, 3 (as amended by Sam), 4, 8 met with recorded output. 2, 5, 6 deferred
on the Phase 2 binary; 7 is Sam's Caddy apply. rehearse.sh 24/24, exit 0.
Adds the deployment mechanism for the portfolio site. The site itself is not
deployed yet: Phase 2 has not produced a binary.
- deploy/deploy.sh: build + test (fail fast) -> releases/<ts> -> atomic
`current` symlink flip -> docker compose up -d -> /healthz gate ->
flip back to `previous` and exit non-zero on failure. `--rollback`
restores the previous release in one command. Auto-detects the Go
toolchain in /nix/store when `go` is not on PATH.
- deploy/docker-compose.yml: 127.0.0.1:8096, mem_limit == memswap_limit ==
120m, capped json-file logs (10m x 3), restart unless-stopped.
- deploy/Dockerfile: runtime image only (alpine + prebuilt payload), so a
release needs no toolchain, no network and no package install.
- deploy/rehearse.sh: repeatable proof (21 assertions, exit 0) of deploy,
build fail-fast, health-gate rollback, --rollback and N3 restart.
- deploy/README.md + docs/phase-3-evidence.md: how it works, the build
contract Phase 2 must satisfy, the Caddy block for Sam, and the open
bind-address question (loopback-only vs reachable from .35).
Satisfies DoD items 1, 3 (container), 4, 8. Items 2, 5, 6, 7 need the Phase 2
binary and Sam's Caddy change; recorded as deferred with owners, not claimed.
Co-Authored-By: Paperclip <noreply@paperclip.ing>