Deploying to production
Run Railbase on a VPS behind a reverse proxy, with TLS and systemd.
Updated
Railbase runs comfortably on a small VPS: one process, one Vault file, and an optional storage directory for uploaded blobs — no database to operate. This guide covers a typical production setup — a reverse proxy for TLS, a systemd unit for supervision, and the production-mode essentials.
Production essentials
Two things separate a dev run from a production run:
export RAILBASE_PROD=true
export RAILBASE_VAULT_PASSWORD_FILE=/run/secrets/railbase-vault
RAILBASE_PROD=trueis the security switch, not just a convenience toggle: it turns on the full production header set — HSTS,X-Frame-Options: DENY,X-Content-Type-Options: nosniff— that a dev run deliberately omits, and it refuses the dev vault key. Treat it as required for any internet-facing deploy.- A real vault password is mandatory — supplied via
RAILBASE_VAULT_PASSWORD_FILE(preferred) orRAILBASE_VAULT_PASSWORD. In production, starting without one is a hard error; Railbase refuses to fall back to the dev key. See Security. .secretmust already exist. Production mode will not mint a fresh master secret, so a brand-new data directory fails to boot with ".secret missing". Initialize the data dir once withoutRAILBASE_PROD(that creates the vault.secret), back up.secret, then run in production — see Installation.
- Uploaded-file encryption is opt-in via
RAILBASE_ENCRYPT_STORAGE=true(its persisted equivalent is thestorage.encrypt_filessetting). Set it before production writes if you want uploaded blobs encrypted at rest as well as the Vault file.
Keep data on local disk:
./railbase serve --addr :8095 --data-dir /var/lib/railbase
Reverse proxy + TLS
Terminate TLS at a reverse proxy and forward to Railbase on :8095. Don't expose
:8095 to the internet directly.
Caddy (automatic HTTPS):
app.example.com {
reverse_proxy 127.0.0.1:8095
}
nginx:
server {
listen 443 ssl;
server_name app.example.com;
# ssl_certificate / ssl_certificate_key ...
location / {
proxy_pass http://127.0.0.1:8095;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_buffering off; # realtime is server-sent events — stream, don't buffer
}
}
Note
The generated TypeScript SDK uses server-sent events over plain HTTP/1.1,
and Railbase also exposes a WebSocket sibling at /api/realtime/ws for clients
that need bidirectional subscription changes. Railbase emits
X-Accel-Buffering: no on the SSE stream; proxy_buffering off is the
belt-and-suspenders equivalent for proxies that don't honour the header.
(Caddy streams SSE and WebSocket correctly out of the box; nginx deployments
that use WebSocket should also forward Upgrade / Connection.)
Important
When you run behind a proxy, set RAILBASE_TRUSTED_PROXIES to your proxy's CIDR
so client IPs (from X-Forwarded-For) are trusted correctly. Without it,
rate-limiting and audit logs see the proxy's IP, not the client's.
systemd unit
[Unit]
Description=Railbase
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/railbase serve --addr :8095 --data-dir /var/lib/railbase
EnvironmentFile=/etc/railbase/railbase.env
Restart=on-failure
RestartSec=2
DynamicUser=yes
StateDirectory=railbase
# data-dir above can point at /var/lib/railbase (StateDirectory)
[Install]
WantedBy=multi-user.target
Put your environment in /etc/railbase/railbase.env (mode 0600):
RAILBASE_PROD=true
RAILBASE_VAULT_PASSWORD_FILE=/run/secrets/railbase-vault
RAILBASE_TRUSTED_PROXIES=127.0.0.1/32
RAILBASE_LOG_FORMAT=json
RAILBASE_ENCRYPT_STORAGE=true
Then systemctl enable --now railbase. (The marketplace/plugin manager is on
by default — you'd only set RAILBASE_PLUGIN_MANAGER=0 to turn it off.)
Note
serve runs under a small in-binary supervisor that owns the health-gated
self-update (it can swap the binary and restart the worker, rolling back if the
new build fails to come up). It's compatible with Restart=on-failure above —
systemd supervises the process, the supervisor supervises in-place updates. See
Updating.
After it's up
- Seed your first admin:
railbase admin bootstrap you@example.com(first-run only — see Quickstart). Add further administrators via the invite flow (admin UI -> Users & access -> Admins & roles -> Invite, orPOST /api/_admin/admins/invite), not the CLI. - Set up a backup schedule — Backups & restore.
- Review Security before exposing the admin and marketplace surfaces.
Tip
Health endpoints GET /healthz and GET /readyz are handy for your load
balancer or uptime checks.