Legendary Arena Lab

Migration Checklist

wiki

Migration Checklist

Migration in progress. api.legendary-arena.com and its PostgreSQL are moving off Render onto a self-hosted DigitalOcean Ubuntu droplet fronted by Cloudflare, per docs/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.

LegWhat it isStatusWhere it lives
GitHubSource of truth for the engine + this lab repo; CI origin for the deploy pipeline✅ In placeengine repo + this lab repo
Cloudflare PagesThe 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 dropletThe api backend (apps/server) + co-located PostgreSQL 18, replacing Render’s web service + managed DB🔲 Not startedinfra/ in this repo

The frame to hold onto: the droplet does not serve playplay 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.

PhaseWorkOwning wiki pagesStatus
0Governance + scaffold (repo, charter, decisions, wiki)— (this repo, DECISIONS.md )✅ Done
1Provision + harden dropletProvision Droplet , SSH Hardening , UFW Firewall , Fail2ban + Upgrades🔲 Not started
2Runtime + Nginx/TLS + PostgresNode Runtime , Nginx Reverse Proxy , Cloudflare TLS , PostgreSQL Setup🔲 Not started
3Deploy vs a COPY of prod DB; smoke-test a live matchDeploy Server🔲 Not started
4Backups + monitoring + deploy pipelineDeploy Pipeline ; backup/monitor scripts planned🔲 Not started — critical gate
5Cutover (Cloudflare DNS flip api. → droplet)orchestration — planned🔲 Not started
6ewiki → Cloudflare Pages; retire Render Static Siteewiki-rehost — planned🔲 Not started
7Decommission 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.yaml and 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 la role + 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/server against a COPY of prod DB; smoke-test a full match (Phase 3) — Deploy Server
  • Wire the GitHub Actions deploy pipeline; set DEPLOY_ENABLED=true once 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 ).
  • play is not on the droplet. Treating play.legendary-arena.com as something to “deploy to Ubuntu” is the trap: it is a Cloudflare Pages SPA and stays there per D-001 . Only api and 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=true before 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