Deploy Server
Deploy Server
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
Clones the app, builds it with the pinned Node toolchain, optionally runs migrations, then installs and starts the la-server systemd unit behind Nginx. This is the step that turns a bare droplet into a running apps/server on 127.0.0.1:3000.
Mechanics
The owning artifact infra/scripts/40-app-deploy.ps1
requires root and requires REPO_URL. It runs in five stages:
Inputs / clone — ensures the operator user exists, installs git, then clones
REPO_URLintoAPP_DIR(default/opt/legendary-arena) or, if already present, fetches and checks outDEPLOY_REF(defaultmain) withgit pull --ff-only— all as the operator user.Build — builds as the operator via fnm, mirroring the Render
buildCommand:/opt/fnm/fnm exec 24.18.0 pnpm install --frozen-lockfile # falls back to a non-frozen install on lockfile mismatch /opt/fnm/fnm exec 24.18.0 pnpm -r buildOptional migrations — runs
node scripts/migrate.mjsonly whenRUN_MIGRATIONS=true(defaultfalse).Install unit — copies
infra/systemd/la-server.serviceto/etc/systemd/system/la-server.service, thensystemctl daemon-reload+systemctl enable la-server.Restart — restarts
la-serverand shows its status.
The installed unit sets ExecStart=/opt/fnm/fnm exec 24.18.0 pnpm --filter @legendary-arena/server start, WorkingDirectory=/opt/legendary-arena, EnvironmentFile=/etc/la/.env, User=operator, Restart=on-failure, and After/Wants postgresql.service. The env file is infra/.env.example
copied to /etc/la/.env mode 600. Environment overrides: OPERATOR_USER, NODE_VERSION (24.18.0), APP_DIR (/opt/legendary-arena), REPO_URL (required), DEPLOY_REF (main), RUN_MIGRATIONS (false), SYSTEMD_SRC. The build mirrors the plan’s deploy build (pnpm install --frozen-lockfile && pnpm -r build) and is heavy on the 8 GB box (docs/PLAN.md
).
Interactions
- Node Runtime
— provides fnm at
/opt/fnm, Node 24.18.0, and pnpm; must run before this deploy or the build has no toolchain. - Systemd Service
— the
la-serverunit this script installs, enables, and restarts. - Secrets and Env
— supplies
/etc/la/.env(mode 600), read via the unit’sEnvironmentFile. - PostgreSQL Setup
— provides the
DATABASE_URLtarget the app and migrations connect to. - Deploy Pipeline — the CI workflow that re-runs this flow on push.
Edge Cases
REPO_URLis required — the script cannot clone or fetch without it./etc/la/.env(mode 600) must exist before start or the unit’sEnvironmentFilefails; see Secrets and Env .RUN_MIGRATIONSdefaultsfalse, so migrations are usually driven by the deploy pipeline instead of this script.- The build runs as the operator via fnm at
/opt/fnm, so Node Runtime must have run first. - A
--frozen-lockfilemismatch falls back to a plain install, which risks silent dependency drift; health is exposed at/healthand/health/legends-publisher.
Execute
# Clone, build, install and start la-server (root required; REPO_URL mandatory)
sudo REPO_URL='https://github.com/legendary-arena/legendary-arena' \
pwsh -File infra/scripts/40-app-deploy.ps1
# Deploy a specific ref and run migrations in the same pass
sudo REPO_URL='https://github.com/legendary-arena/legendary-arena' \
DEPLOY_REF='release-2026-07' RUN_MIGRATIONS=true \
pwsh -File infra/scripts/40-app-deploy.ps1
Verify
# Unit is active
systemctl status la-server --no-pager
# Expected: Active: active (running)
# App answers on the loopback health endpoint
curl -s localhost:3000/health
# Expected: a 200 health payload (e.g. {"status":"ok"})
# Recent unit logs show a clean start
journalctl -u la-server -n 50
# Expected: startup lines, no repeated Restart=on-failure loops
References
infra/scripts/40-app-deploy.ps1— owning deploy script.infra/systemd/la-server.service— unit installed by the script.infra/.env.example— template copied to/etc/la/.envmode 600.docs/PLAN.md— migration plan, Node/pnpm pins, build-mirror and 8 GB box facts.content/app/deploy-server.md— source operator page.