Migration Checklist
Migration Checklist
Migration in progress.
api.legendary-arena.comand its PostgreSQL are moving off Render onto a self-hosted DigitalOcean Ubuntu droplet fronted by Cloudflare, perdocs/PLAN.md. GitHub and Cloudflare stay; only the server, its database, and the ewiki leave Render. Until decommission, Render stays warm as the rollback target.
Summary
This page is the live progress view of the Render → DigitalOcean migration: where the program is against the docs/PLAN.md §6
phase sequence, and what the next actionable step is. It is a status overlay on the plan, not a second copy of it — the plan and D-001
govern; this page only tracks completion. The goal it drives toward is the game fully playable in production with the Render dependency retired: the play frontend (and the other SPAs) already served from Cloudflare Pages, talking to the api backend now running on the droplet instead of Render.
Mechanics
The stack, and what’s already done
Three legs carry the playable game to production. Two are in place today; the third — the droplet — is the whole of this migration.
| Leg | What it is | Status | Where it lives |
|---|---|---|---|
| GitHub | Source of truth for the engine + this lab repo; CI origin for the deploy pipeline | ✅ In place | engine repo + this lab repo |
| Cloudflare Pages | The five SPAs — play, www, cards, legends, dashboard — served at the edge; play.legendary-arena.com is one of them | ✅ In place — unchanged by this migration (D-001 ) | engine repo → Cloudflare Pages |
| Ubuntu droplet | The api backend (apps/server) + co-located PostgreSQL 18, replacing Render’s web service + managed DB | 🔲 Not started | infra/
in this repo |
The frame to hold onto: the droplet does not serve play — play is a static SPA on Cloudflare Pages and stays there. The droplet serves the api that play calls. “Get play running in prod” reduces to “stand up the api backend on the droplet and cut api. over to it.” See Render-to-DigitalOcean Migration
for the locked architecture.
Phase status (keyed to PLAN §6)
Every on-droplet phase is Not started because nothing has run against a real host yet — consistent with the wiki’s status discipline
(draft until verified on a live droplet). Phase 0 is done: this repo, its governance, and the wiki exist.
| Phase | Work | Owning wiki pages | Status |
|---|---|---|---|
| 0 | Governance + scaffold (repo, charter, decisions, wiki) | — (this repo, DECISIONS.md ) | ✅ Done |
| 1 | Provision + harden droplet | Provision Droplet , SSH Hardening , UFW Firewall , Fail2ban + Upgrades | 🔲 Not started |
| 2 | Runtime + Nginx/TLS + Postgres | Node Runtime , Nginx Reverse Proxy , Cloudflare TLS , PostgreSQL Setup | 🔲 Not started |
| 3 | Deploy vs a COPY of prod DB; smoke-test a live match | Deploy Server | 🔲 Not started |
| 4 | Backups + monitoring + deploy pipeline | Deploy Pipeline ; backup/monitor scripts planned | 🔲 Not started — critical gate |
| 5 | Cutover (Cloudflare DNS flip api. → droplet) | orchestration — planned | 🔲 Not started |
| 6 | ewiki → Cloudflare Pages; retire Render Static Site | ewiki-rehost — planned | 🔲 Not started |
| 7 | Decommission Render (after rollback window) | cleanup — planned | 🔲 Not started |
The Phase 4 backup/monitoring scripts (50-backup.ps1, 51-restore.ps1, 52-vacuum-reclaim.ps1, 60-monitor.ps1) and the Phase 5/6 orchestration are planned but not yet in infra/
, so they carry no wiki page yet (INDEX.md § Not yet documented
).
The critical path is the tested restore — not provisioning
Standing up Ubuntu is the easy 80%; the Ubuntu Droplet Setup
sequence gets you a running api on the box. The item that actually gates cutover is Phase 4’s restore drill: PLAN §6.1 makes “restore drill passed” the Phase 4 exit criterion, and the migration hub states the invariant plainly — “a restore you have not tested is not a backup” (Render-to-DigitalOcean Migration § Edge Cases
). Do not schedule cutover (Phase 5) until a pg_dump from R2 has been restored into a throwaway Postgres and validated. The backup/restore scripts do not exist yet — that gap, not provisioning, is the real distance to a safe cutover.
Next actionable steps — droplet bring-up
The near-term work, in order. Each maps to a wiki page under Ubuntu Droplet Setup :
- Fill the operator public key in
infra/cloud-init.yamland create the droplet (Ubuntu 24.04,sfo3) — Provision Droplet - Harden SSH; confirm operator access from a clean shell before closing the anti-lockout session — SSH Hardening
- Enable UFW (default-deny; SSH + 80/443) — UFW Firewall
- Configure fail2ban + unattended-upgrades — Fail2ban + Upgrades
- Snapshot the droplet (
post-bootstrap-hardened, PLAN §5) — cheap rebuild insurance - Install Node 24.18.0 via fnm + pnpm — Node Runtime
- Install PostgreSQL 18 on localhost; create the
larole + DB — PostgreSQL Setup - Configure Nginx to proxy
api.→127.0.0.1:3000(plain HTTP first) — Nginx Reverse Proxy - Install the Cloudflare Origin Certificate, set Full (strict), narrow UFW 443 to Cloudflare IPs — Cloudflare TLS
- Deploy
apps/serveragainst a COPY of prod DB; smoke-test a full match (Phase 3) — Deploy Server - Wire the GitHub Actions deploy pipeline; set
DEPLOY_ENABLED=trueonce the host + three deploy secrets exist — Deploy Pipeline - Write + pass the backup/restore drill (Phase 4 exit gate) — scripts planned, not yet in
infra/ - Cutover: flip Cloudflare DNS
api.→ droplet, Render kept warm (Phase 5) - Decommission Render after the rollback window (Phase 7)
Interactions
- Render-to-DigitalOcean Migration — the canonical concept hub this checklist tracks progress against; it holds the locked architecture and rationale.
- Ubuntu Droplet Setup — the ordered bring-up guide for the droplet leg; the checklist’s Phase 1–2 rows are its steps.
docs/PLAN.md— §6 phase sequence and §6.1 exit criteria that define “done” for each phase.docs/DECISIONS.md— D-001, which fixes what moves (server + DB + ewiki) vs what stays (GitHub, the five Cloudflare Pages SPAs).
Edge Cases
- This page tracks status; it does not define it. If a checkbox here disagrees with the plan’s phase exit criteria, the plan wins and this page is stale — update it, don’t argue with it (SCHEMA.md § Authority Position ).
playis not on the droplet. Treatingplay.legendary-arena.comas something to “deploy to Ubuntu” is the trap: it is a Cloudflare Pages SPA and stays there per D-001 . Onlyapiand PostgreSQL move.- “Provisioned” is not “ready to cut over.” A green Phase 1–3 with no tested restore (Phase 4) is a box that runs, not a safe replacement for Render. The restore drill is the gate.
- Don’t enable the deploy pipeline early. Setting
DEPLOY_ENABLED=truebefore the droplet and its three secrets (DEPLOY_HOST,DEPLOY_USER,DEPLOY_SSH_KEY) exist just restores the red-X failures the gate was added to prevent (Deploy Pipeline § Edge Cases ). - Distant-future timing. There is no cost pressure to rush — Render (~$44/mo) and the droplet ($24–48/mo) are a wash; control and capability are the reason to move, not savings (D-001 ).
References
docs/PLAN.md— §6 migration sequence and §6.1 phase exit criteria.docs/DECISIONS.md— D-001: scope (what moves vs stays), locked architecture, rollback.- Render-to-DigitalOcean Migration — the concept hub and locked-decisions summary.
- Ubuntu Droplet Setup — the ordered droplet bring-up guide.