Render-to-DigitalOcean Migration
Render-to-DigitalOcean Migration
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. Until decommission, Render stays warm as the rollback target.
Summary
This migration moves apps/server and its managed PostgreSQL off Render onto a single self-hosted DigitalOcean droplet fronted by Cloudflare, and relocates the ewiki static site to Cloudflare Pages (PLAN.md
). Only those three things move — the five Cloudflare Pages SPAs (www, play, cards, legends, dashboard) are unchanged (PLAN.md
). Once the droplet has carried real traffic through the rollback window, Render is retired (PLAN.md
).
Mechanics
The target is one droplet doing everything: Ubuntu 24.04 LTS, 4 vCPU / 8 GB (~$48/mo, with a step down to 4 GB ~$24 if it sits idle), in region sfo3 (PLAN.md
). The application and its database are co-located on that single box.
Locked decisions (PLAN.md ):
- App —
apps/server(boardgame.io / Socket.IO / Koa, Node 24.18.0) runs under systemd behind Nginx. - DB — PostgreSQL 18, self-hosted, bound to localhost on the same box.
- TLS — Cloudflare proxied (orange-cloud), Origin Certificate with Full (strict) on Nginx; UFW locks 443 to Cloudflare IPs only.
- Backups —
pg_dumpcron pushing to Cloudflare R2, with a mandatory restore drill before cutover. - Deploy — GitHub Actions → SSH →
git pull+ build +systemctl reload, replacing Render’s push-to-main flow. - Monitoring — an external uptime check firing a Brevo alert.
- Secrets — roughly 15
sync:falsevars land in a600-perm.envon the box.
The cost read is honest: Render runs about $25 server + $19 DB ≈ $44/mo against the droplet’s $24–48, so it’s roughly a wash — the driver is control and capability, not the bill (PLAN.md ). Moving to self-hosting means accepting ownership of backups, patching, monitoring, DR, and TLS, plus the single-box SPOF, mitigated by tested R2 backups (PLAN.md ).
Rebuild invariant: “rebuild the host from documentation alone” means “run the numbered infra/scripts/*.ps1 in order” (PLAN.md
). Note that the backups and monitoring scripts (50/51/52/60) referenced by the plan are planned and not yet present in infra/; they are described here, not linked.
Interactions
Two companion pages sit across the whole arc: Ubuntu Droplet Setup is the ordered Phase 1–3 bring-up guide for the droplet, and Migration Checklist is the live progress view against the PLAN §6 phases.
Each sibling wiki page owns one slice of the arc (PLAN.md ):
- Provision Droplet — Phase 1: stands up the Ubuntu droplet via cloud-init, the foundation everything else runs on.
- SSH Hardening — Phase 1: locks down remote access on the freshly provisioned box before it holds anything real.
- Nginx Reverse Proxy — Phase 2: fronts the systemd app service and terminates the Cloudflare origin connection.
- Cloudflare TLS — Phase 2: wires the Origin Certificate and Full (strict) so traffic is proxied end-to-end.
- PostgreSQL Setup — Phase 2: installs and configures the co-located PostgreSQL 18 on localhost.
- Deploy Server — Phase 3: deploys the server against a copy of prod DB and smoke-tests a live match as a no-impact dress rehearsal.
- Deploy Pipeline — Phase 4: replaces Render’s push-to-main with the GitHub Actions → SSH → reload pipeline.
Edge Cases
- Single-box SPOF — app and DB share one droplet, so the box is a single point of failure; the mitigation is tested R2 backups, not redundancy (PLAN.md ).
- Cost is a wash — do not sell this on price. Render and the droplet land in the same range; control and capability are the reason to move (PLAN.md ).
- Cutover DNS-flip risk — cutover flips Cloudflare DNS for
api.from Render to the droplet; if it goes wrong, the rollback is to repoint DNS back to Render, which is why Render is kept warm (PLAN.md ). - A restore you haven’t tested is not a backup — the restore drill is mandatory before cutover; a
pg_dumpyou’ve never restored buys nothing (PLAN.md ). - You now own the platform layer — patching, DR, and TLS that Render handled are yours after this migration (PLAN.md ).
History
The migration runs in ordered phases (PLAN.md ):
- Phase 0 — governance and scaffold.
- Phase 1 — provision and harden the droplet (cloud-init, scripts 05/06/07).
- Phase 2 — runtime plus Nginx/TLS and Postgres (scripts 10/20/21/30).
- Phase 3 — deploy the server against a copy of prod DB and smoke-test a live match — a dress rehearsal with no prod impact.
- Phase 4 — backups, monitoring, and the deploy pipeline (scripts 50/51/52/60,
deploy.yml). - Phase 5 — cutover: final
pg_dump→restore, flip Cloudflare DNSapi.→droplet, verify, with Render kept warm. - Phase 6 — move ewiki to Cloudflare Pages.
- Phase 7 — decommission Render after the rollback window; Render services are not deleted until the droplet has carried real traffic.
Non-goals for this migration: no app/DB host split, no change to the 5 SPAs, and no app feature work beyond the safe hosting move (PLAN.md ).
References
- PLAN.md — authoritative source (goal, locked decisions, phases, rollback, cost, non-goals).
- 01-REPO-FOLDER-STRUCTURE.md
— repo and
infra/scriptslayout. - README.md — program overview.
- Sibling wiki pages: provision-droplet.md , ssh-hardening.md , nginx-reverse-proxy.md , cloudflare-tls.md , postgres-setup.md , deploy-server.md , deploy-pipeline.md .