The full server setup.
The quick start gets it running on a laptop. This is what changes when it needs to survive a reboot and face the internet.
What the server needs.
Node
A current LTS release. The agent build and the WebSocket process both run on it.
PostgreSQL
One database. Keep it on localhost, or behind a private network.
A reverse proxy
Apache, Nginx or Caddy — anything that can forward a WebSocket upgrade. This is the step people get wrong.
A process manager
PM2 is what the repository ships configuration for, running the web app and the WebSocket process as two apps.
Two required, the rest optional.
Required
DATABASE_URLPostgreSQL connection string.
JWT_SECRETSession signing secret. Unique per deployment — a session minted by one deployment must not be valid on another.
Optional
NEXT_PUBLIC_APP_URLPublic URL of the web app.
NEXT_PUBLIC_WS_URLPublic WebSocket URL, usually the app URL with a /ws prefix.
PORTWeb app port. Defaults to 50051.
WS_PORTWebSocket server port. Defaults to 50052.
WS_HOSTBind address for the WebSocket server. Leave it on 127.0.0.1 unless it runs on a different host — and firewall it if you change it.
WS_INTERNAL_TOKENToken for the app-to-WebSocket relay. Derived from JWT_SECRET when unset, identically on both sides.
SMTP_HOST / PORT / USER / PASSOutbound mail for PIN resets and share invitations.
FROM_EMAIL / FROM_NAMESender identity on those emails.
VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECTWeb push. Push is disabled rather than broken when unset — but use the same pair in development and production, or existing subscriptions stop working.
Two upstreams, one of them a WebSocket.
The web app and the WebSocket server are separate processes on separate ports. Route / to the app and /ws/ to the WebSocket server, and make sure the Upgrade and Connection headers survive the hop. Terminals connecting and then doing nothing is almost always this.
Terminate TLS at the proxy. Sessions and terminal traffic are not safe over plain HTTP.
Build, then start both processes.
$ npm run build$ npm run pm2:startOn a server that shares its database with another environment, use the migration scripts rather than db:push — the latter diffs the schema and will drop what it does not recognise.
Before you point a real machine at it.
- JWT_SECRET is strong and unique to this deployment.
- TLS terminates in front of the app, and HTTP redirects to HTTPS.
- The WebSocket server is on 127.0.0.1, or firewalled if it is not.
- PostgreSQL is not listening on a public interface.
- Every account has two-factor enrolled — the API enforces it, but check nobody is stuck half-enrolled.
- You know what is currently tunnelled. Anything tunnelled is reachable by anyone with the URL.