Skip to content

Preview/prod split on the NAS (Vercel-style) — p-eop=nightly, eop=main

Why

Mimic Vercel's preview/prod split on the self-hosted NAS: p-eop.theestablishers.com serves the nightly branch (preview), eop.theestablishers.com serves main (prod). Follow-on to T-073.

Status (2026-06-21)

  • Cloudflare (via CF API, token CLOUDFLARE_API_TOKEN): added p-eop.theestablishers.com to the roofkeeper-nas tunnel ingress → http://localhost:3001, plus a proxied DNS CNAME. Preserved the existing nas. and eop. routes. Verified: p-eop serves the app (307 → /auth/signin, NextAuth host = p-eop).
  • Containers: eop-app-preview runs on localhost:3001 with --memory 600m; prod eop-app capped at 1100m. Neither can fry the NAS — a runaway gets its container OOM-killed, host survives.
  • RAM monitor: ram-monitor container logs host MemAvailable/SwapFree every 5 min to /docker/eop/ram-monitor.log. At idle both apps fit (~230 MB free, modest swap).
  • ⏳ Both currently run :latest (= nightly), so prod and preview are the same code for now.
  • ⚠️ RAM is tight; if the monitor shows sustained pressure / swap-thrash, drop the preview (prod + host unaffected).

Decision log

Retrofit note (added 2026-06-21 by Accounting (Diagnostics)): the entry below was authored by the EOP Local Assistance agent, per its Attestation (EOP Local Assistance): byline. The **Source:** <name> · <session URL> convention was added to AGENTS.md mid-2026-06-21 — after EOP Local Assistance had already opened this task — so its session URL is not recorded here. If EOP Local Assistance re-opens this task in a future session it can append its own Source line. Source (edit): Accounting (Diagnostics) · https://claude.ai/code/session_01G58Y71noihrYCDEDMexmea

2026-06-21 — split set up (Cloudflare + capped preview + RAM monitor)

  • Attestation (EOP Local Assistance): read AGENTS.md and scanned the board — no existing task covered the preview/prod split, so opened T-082 (follow-on to T-073); kept current as work landed.
  • Source: EOP Local Assistance · session 63c32698-0f80-464c-a668-f352be2c148e (local Claude Code)
  • What changed: added the p-eop tunnel ingress + DNS; stood up a memory-capped preview container + a standing RAM monitor; capped prod.
  • Proposed by: the owner. Approved by: the owner.
  • Rationale: the owner wants a Vercel-style preview/prod split self-hosted; the 1.9 GB NAS is tight, so proceed only behind memory caps (preview can't fry the NAS) + a monitor + a drop-if-it-suffers fallback.
  • Evidence — owner, 2026-06-21 (verbatim):

    "Is it actually possible that … the NAS mimics Vercel by having p-eop.theestablishers.com to run on the GitHub branch nightly while eop.theestablishers.com run on main?"

"if it wouldn't fry my NAS for possibly overloading it, I'd actually like you to proceed without the RAM upgrade, while have measure implemented to monitor the RAM usage just to make sure the performance suffice, or else we'll just drop the split"

Next (to make it a real nightly-vs-main split)

  1. Per-branch CI — build nightlyeop-app:nightly, maineop-app:main; preview pulls :nightly, prod pulls :main (auto-build on push).
  2. Promote nightlymain (owner OK required — a push to main) so eop=main is current, else prod would revert ~104 commits.
  3. Watch /docker/eop/ram-monitor.log; drop the preview if it thrashes.

Note (2026-06-21) — p-eop Firebase auth error is NOT the split → see ticket I-002

  • My earlier diagnosis here was wrong. p-eop's Firebase sign-in error ("Unable to save initial state / sessionStorage") is not caused by the split: p-eop is already a Firebase authorized domain, the /auth/signin response headers are byte-identical to eop's (so it isn't Cloudflare), and the sign-in code uses window.location.origin (not the baked NEXT_PUBLIC_APP_URL). It's a Firebase signInWithRedirect cross-domain authDomain + browser storage-partitioning issue — tracked as ticket I-002 (escalates to a T-NNN when fixed). The preview-image rebuild below is still worth doing for correctness, but it won't fix that auth error.

2026-06-22 — split is LIVE; preview is OOM-thrashing

  • Attestation (EOP Local Assistance): read AGENTS.md; tracking T-082, kept current.
  • Source: EOP Local Assistance · session 63c32698-0f80-464c-a668-f352be2c148e (local Claude Code)
  • Split live: the repoint completed on the NAS ~2026-06-21T18:08 (after a long pull stall that jammed the DSM channel for hours — only the channel result was lost; the docker recreate ran). eop-app → eop-app:main (eop-baked, restarts=0, stable) + eop-app-preview → eop-app:nightly (p-eop-baked). Both serve 307. Verified via the recovered DSM channel.
  • ⚠️ Preview thrashing: eop-app-preview has 11 OOM-restarts — the 600 MB cap is too low for the Next.js server (OOMs ~every 40 min). The cap contains it: prod (eop-app, 1100 MB) is unaffected, the host can't be fried. Degraded-but-safe preview, not a host risk.
  • Decision (owner, 2026-06-22): KEEP the preview running. Asked the owner keep / stop-but-configured / drop; they chose keep — the thrash is contained (prod + host safe), and p-eop stays available for T-047 / I-002 testing. Revisit only if the NAS gets a RAM upgrade (then raise the cap) or if the owner later wants it dropped.

DONE — 2026-06-22

  • Attestation (EOP Local Assistance): read AGENTS.md; closing T-082 with verdict + commits + blast radius per the "close the loop" rule.
  • Source: EOP Local Assistance · session 63c32698-0f80-464c-a668-f352be2c148e (local Claude Code)
  • Verdict — what landed vs. plan: the Vercel-style preview/prod split is live and current. eop.theestablishers.com serves main (prod), p-eop.theestablishers.com serves nightly (preview), both on the NAS behind the roofkeeper-nas Cloudflare tunnel. Per-branch CI bakes a distinct image per branch (NEXT_PUBLIC_APP_URL set per ref); both containers are memory-capped (prod 1100 MB, preview 600 MB) with a standing RAM monitor. Verified via the DSM channel: eop-app on :main (restarts=0, stable), eop-app-preview on :nightly (serving 307); both 307 → /auth/signin. The preview self-OOMs (~11 restarts) but the cap contains it — prod + host unaffected; owner chose to keep it.
  • Commits: 4b13062d (per-branch CI — main→eop-app:main, nightly→eop-app:nightly); eop=main was made current by a fast-forward ref move of main to nightly's tip (owner-authorized push to main, not a new commit); decision-log + close-out docs in 7c12369d + this entry. The earlier setup (Cloudflare ingress/DNS, capped containers, RAM monitor) was applied via the CF API + the NAS, not git.
  • Blast radius for other agents:
  • .github/workflows/nas-image.yml now builds two images keyed by branch — anyone changing the deploy/CI path must keep both :main and :nightly tags + the per-branch NEXT_PUBLIC_APP_URL baking.
  • main is now ~current with nightly (was ~104 commits behind) — prod (eop) reflects nightly's state as of the promotion; future main divergence is by design (promote nightly→main to ship).
  • NAS RAM is the binding constraint (1.9 GB): the preview runs hot. Any new always-on container on the NAS competes for the same headroom — coordinate before adding one.
  • Not in scope here: p-eop's Firebase sign-in error is I-002 / T-083 (same-origin authDomain), not the split. Testing auth flows on p-eop waits on T-083.

Post-close note — 2026-06-22 — branches kept in LOCKSTEP (owner directive)

  • Source: EOP Local Assistance · session 63c32698-0f80-464c-a668-f352be2c148e (local Claude Code)
  • The split infrastructure built here stays in place, but per an owner directive the branch routing is unified for now: main and nightly are kept current together (ship to main) until further notice — the owner isn't using p-eop. The live policy + the owner's verbatim instruction are in the 🚦 Operational signals banner at the top of AGENTS.md. Blast radius: every push now reaches prod (eop); the preview rebuilds too (unused). Flip that signal to 🔴 to re-separate — no rebuild needed.

Future direction (NOT yet decided) — 2026-06-22 — prod→Vercel, nightly→NAS

  • Source: EOP Local Assistance · session 63c32698-0f80-464c-a668-f352be2c148e (local Claude Code)
  • The owner floated a contingency: move prod back to Vercel (managed / always-on) and run the nightly/preview on the NAS. Affirmed as sound — correct risk allocation (prod off a single 1.9 GiB box shared with home-automation; the NAS keeps the throwaway preview). The original reason for leaving Vercel (Hobby compute) was largely addressed by T-073's de-intensification + the 2-user base; remaining prep = deploy the pending Firestore index, leave crons on the NAS (T-084), bank-login already external.
  • Coordination note for other agents: don't over-invest in hardening prod-on-NAS — prod's likely long-term home is Vercel again. Not actioned; kept in mind.
  • Owner, 2026-06-22 (verbatim):

    "worst case, we could have prod re-deployed completely to Vercel, while we run nightly on my NAS some day (if you affirm then I'll keep this in mind)"

2026-06-22 — preview DROPPED (NAS resource relief)

  • Source: EOP Local Assistance · session 63c32698-0f80-464c-a668-f352be2c148e (local Claude Code)
  • Owner: "Let's drop the preview image on my NAS." Done — verified no auto-recreate first (no watchtower, no cron, no deploy script; the container was a manual docker run), so it stays down:
  • Container eop-app-preview stopped + removed; image eop-app:nightly removed from the NAS.
  • CI nas-image.yml trigger is now branches: [main]nightly no longer auto-builds an image (manual workflow_dispatch on the nightly ref still can). The nightly branch + the task-board build (docs-deploy.yml) are unaffected.
  • Cloudflare p-eop DNS CNAME deleted (verified gone; eop + nas DNS confirmed intact). The tunnel ingress rule (p-eop → localhost:3001) was left dormant on purpose — harmless without DNS, ready if the preview is revived; safer than editing the shared tunnel config that also routes prod.
  • Result (verified): prod eop-app (:main) still 307/healthy; 3001 refused; host RAM available 322 → 721 MiB (used 1401 → 1004 MiB). The OOM-restart churn is gone.
  • Blast radius: p-eop is down by design. This closes I-002 (symptom can't recur without the domain) and downgrades T-083 to latent prod-auth hardening (low priority — no active symptom; prod popup sign-in is the working path). Branch policy unchanged (🟢 lockstep, ship to main). Minor follow-up: the docs/deploy/nas-runbook.md preview section is now stale.