Railbase
GPTClaude

Deploying to production

Run Railbase on a VPS behind a reverse proxy, with TLS and systemd.

Updated

Video guide —watch on YouTube ↗

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=true is 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) or RAILBASE_VAULT_PASSWORD. In production, starting without one is a hard error; Railbase refuses to fall back to the dev key. See Security.
  • .secret must 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 without RAILBASE_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 the storage.encrypt_files setting). 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, or POST /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.

Was this page helpful?Thanks for your feedback!