Lightsail Node.js host
This is the API box for re360muse-backend. RDS and R2 stay off this
disk. Do not use the Bitnami Node.js blueprint — Ubuntu 24.04,
same region as RDS (eu-west-1).
scripts/deploy.sh is the RealCredi-style installer. First run: apt
upgrade, Node 20, Caddy, GitHub deploy key, clone, openssl rand -base64 32 for JWT / session / DB password, write .env, migrate deploy, systemd. Later releases: push main (self-hosted GitHub
Actions runner on this box) or scripts/update.sh. Existing .env is
never overwritten.
1. Create the instance
- Lightsail → Create instance → Ubuntu 24.04 → 2–4 GB →
re360muse-api. - Attach a static IP.
- Networking: 22 from your IP; 80/443 open; not 4000 or 5432.
- Account → Advanced → VPC peering. RDS SG: 5432 from the Lightsail CIDR.
2. First boot (from your laptop)
Copy the script, then run it as root on the box. It will print an SSH
public key — add that as a read-only deploy key on
MuseTec/re360muse-backend, press Enter, and it clones.
scp scripts/deploy.sh ubuntu@STATIC_IP:/tmp/re360muse-deploy.sh
ssh ubuntu@STATIC_IP
sudo bash /tmp/re360muse-deploy.sh \
--db-host re360muse.XXXX.eu-west-1.rds.amazonaws.com \
--s3-endpoint https://ACCOUNT_ID.r2.cloudflarestorage.com \
--aws-access-key "$AWS_ACCESS_KEY_ID" \
--aws-secret-key "$AWS_SECRET_ACCESS_KEY" \
--paystack-secret-key "$PAYSTACK_SECRET_KEY" \
--rds-admin-user re360muse_admin \
--rds-admin-password "$RDS_MASTER_PASSWORD" \
--seed
--db-password is optional. If omitted, the script generates one with
openssl rand -base64 32 and builds:
DATABASE_URL=postgresql://re360muse_app:GENERATED@HOST:5432/re360muse?sslmode=require
--rds-admin-password applies create-app-user.sql over SSL. If you
skip it, the SQL file is at /opt/re360muse/create-app-user.sql (mode
600) — run that as the master user from Lightsail, then re-run deploy
if migrate failed.
Secrets are written to /opt/re360muse/api/.env mode 600 and not
printed. Paystack and R2 keys are the flags you passed; JWT and
session are generated.
Cloudflare Origin CA → /etc/caddy/certs/origin.pem and origin.key.
DNS api A record → static IP, proxied, Full (strict), WebSockets
on.
3. Later releases
Push or merge to main on MuseTec/re360muse-backend. GitHub
Actions compiles on a hosted runner, then a self-hosted runner on
this box runs scripts/update.sh --branch main. develop does not
deploy. SSH can stay limited to your IP — the runner dials out to
GitHub.
One-time runner
Mint a registration token (expires in one hour; do not commit it):
gh api -X POST repos/MuseTec/re360muse-backend/actions/runners/registration-token --jq .token
On the box. If install-github-runner.sh is not in the clone yet, scp it
first (same pattern as first-boot deploy.sh):
scp scripts/install-github-runner.sh ubuntu@STATIC_IP:/tmp/install-github-runner.sh
ssh ubuntu@STATIC_IP
GH_RUNNER_TOKEN=... sudo bash /tmp/install-github-runner.sh
# later, after main has the script:
# GH_RUNNER_TOKEN=... sudo bash /opt/re360muse/api/scripts/install-github-runner.sh
Repo Settings → Actions → Runners should show re360muse-api idle.
GitHub → Actions → Deploy Lightsail API → Run workflow is the
manual trigger. Do not open port 22 to GitHub-hosted IPs.
Manual fallback
sudo /opt/re360muse/api/scripts/update.sh
# same as:
sudo /opt/re360muse/api/scripts/deploy.sh
update.sh always npm ci (lockfile). prisma migrate deploy — not
migrate dev / db push. --upgrade-os if you also want apt upgrade.
--rotate-secrets regenerates JWT/session only.
If git pull has not landed a migration that production already needs
(Prisma P2021), copy that folder from the laptop into
/opt/re360muse/api/prisma/migrations/ then run npx prisma migrate deploy on the box (same pattern as a normal update). Commit the folder
so the next update.sh keeps it.
curl -fsS http://127.0.0.1:4000/api/health
journalctl -u re360muse-api -f
Node binds 127.0.0.1:4000 in production (HOST). /api/docs is not
served on this host. After first boot, tighten the box:
sudo bash /opt/re360muse/api/scripts/harden-host.sh
That enables UFW (22/80/443), fail2ban for sshd, and deletes leftover
create-app-user.sql. It does not remove Lightsail’s ubuntu NOPASSWD:ALL — stripping that without an ubuntu password locks sudo.
Paystack webhook:
https://api.re360muse.com/api/v1/payments/webhooks/paystack
4. Not on this box
Postgres data directory, upload files as source of truth, .env in
git, public 5432 or 4000.