Server Provisioning & Host Migration
Turn a blank server into a running Social Live host with one command, and the playbook for moving hosts.
Last updated Thu Jul 16 2026 00:00:00 GMT+0000 (Coordinated Universal Time)
TL;DR -A fresh, empty server becomes a fully working Social Live host with one settings file and one command. Nothing is hand-configured on the box, so the server is treated as disposable and rebuildable at any time.
Who this is for -DevOps & Operations: the how-to and the migration playbook. Executive & Product: reassurance that we can rebuild or move the whole service quickly and predictably. Engineering: what the script actually does and the invariants it enforces.
The guiding idea is that the server is disposable. We never log into the box and tweak settings by hand -if we did, that box would become a fragile one-of-a-kind that nobody could recreate. Instead, all of its configuration (the web front door, firewall, security certificates, scheduled jobs) lives in code in the repo. A brand-new Ubuntu or Debian machine becomes a serving host from one env file plus one command, and you can throw the old one away.
Current hosts
We run two boxes: production (the real service) and staging (a safe copy for testing). They must never share credentials.
| Role | Box | Domains |
|---|---|---|
| Production | socialliveserver (Hetzner, 195.201.175.226) | api / admin / liveroom .social-live.app |
| Staging | myvps2 (162.35.163.200) | api-staging / admin-staging / liveroom-staging .social-live.app |
Staging runs sandbox IAP, local-disk media, fresh secrets, and an empty
database -never point it at production credentials. The old owletvpn.com
production stack was archived and decommissioned 2026-07-15 (final
pg_dump + media tarball on both boxes: legacy-myvps2-archive).
How it works
Provisioning a new host
The whole setup is these four steps: clone the repo, copy the settings template, fill in the real values, run the script. The comments below flag the settings that are specific to each individual box and easy to get wrong.
git clone <repo-url> /root/social-live && cd /root/social-live
cp .env.production.example .env.production && chmod 600 .env.production
# Fill in real values. The most host-specific:
# API_DOMAIN / ADMIN_DOMAIN / LIVEROOM_DOMAIN / DOCS_DOMAIN -must already
# resolve to this box (certbot validates over HTTP).
# SFU_ANNOUNCED_IP -THIS box's public IP. Wrong value = live rooms
# join fine but carry no audio/video.
# LETSENCRYPT_EMAIL -certbot registration address.
scripts/provision-server.sh --with-backup-cron
The script is idempotent -meaning you can safely run it again and again with no
harm -so re-run it after changing .env.production or the nginx template.
Flags: --with-backup-cron installs the nightly backup job (see
Backup & Restore); --skip-certbot skips TLS
issuance (e.g. rehearsing behind a firewall).
Here is everything the script does, in order, so nothing about the box is a
mystery: apt packages (docker, nginx, certbot, ufw) → firewall (22/80/443 tcp +
SFU UDP range) → renders deploy/nginx/social-live.conf.template via envsubst
→ docker compose up -d --build → waits for the backend → issues Let's Encrypt
certs → optional backup cron. It refuses to run with placeholder secrets and
warns if SFU_ANNOUNCED_IP doesn't match the detected public IP.
nginx is templated
The web front door (nginx) is generated from a template in the repo, not written by hand on the server. If you edit the copy on the server, the next run of the script overwrites it -so always change the template.
deploy/nginx/social-live.conf.template is the source of truth; the
rendered copy at /etc/nginx/sites-available/social-live.conf must never
be edited on the server -change the template, re-run the script. The
template is HTTP-only; certbot injects the TLS blocks and redirects.
(deploy/nginx/social-live.conf is the frozen record of the original
owletvpn host's hand-made config and is not used by the script.)
No URL fallbacks in docker-compose
This is a safety rule that stops an old domain from silently leaking into a new environment: the compose file has no built-in default hostnames, so a missing value fails loudly instead of quietly using the wrong one.
docker-compose.prod.yml has no baked-in host defaults:
PUBLIC_APP_URL, ADMIN_API_BASE_URL, LIVE_ROOM_SIMULATOR_API_URL,
and LIVE_ROOM_SIMULATOR_SOCKET_URL are ${VAR:?}-required so a stale
domain can never silently leak into a new environment. Every compose
invocation needs --env-file .env.production (the deploy scripts and the
provision script already pass it). If a compose command fails with
required variable ... is missing, the host's .env.production predates
this rule -add the missing variable.
Verifying a freshly provisioned host
Four checks prove the box actually works. The last one is the important one — it's the only test that proves live media is really flowing.
curl -sS https://<API_DOMAIN>/answers over TLS.- Admin portal logs in with
ADMIN_ACCESS_TOKEN. https://<DOCS_DOMAIN>/loads the docs portal publicly (no login).- Two-device live room -host on one device, join from another,
confirm audio and video both ways. This is the only check that proves
SFU_ANNOUNCED_IPand the UDP firewall range; every other check passes without them.
Moving production to a new host
When we need to move the live service to a different server, this is the step-by-step playbook. The goal is a clean cutover with minimal downtime and no lost data.
- Provision the new box as above (new or same domains). Verify fully.
- Lower DNS TTL to 60s the day before.
- Cutover: stop the old backend → final
pg_dump+ volume tars (see Backup & Restore) → restore on the new box → repoint DNS. - Copy secrets exactly: new
JWT_*secrets log every user out; a newOTP_SECRETinvalidates in-flight codes. - In-progress live rooms drop and reconnect at cutover -expected;
WebRTC media targets the raw
SFU_ANNOUNCED_IP, not the domain. - Reinstall the backup cron (
--with-backup-cron) and confirmlast-backup-statusnext morning.
Rehearse this quarterly together with the restore drill: provisioning a throwaway box from scratch is the only proof the RTO (recovery time objective — how fast we can be back up) is real.
Related pages
- Infrastructure -the container topology this script stands up.
- Environment variables -every value that goes into
.env.production. - Backup & Restore -the dumps used during migration.
- Operations -running the box once it's provisioned.