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 data file, 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 turns off development conveniences.
  • 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.

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

Realtime delivery uses server-sent events over plain HTTP/1.1, not WebSocket — so no Upgrade/Connection headers are needed. Railbase already emits X-Accel-Buffering: no on the event stream, which tells nginx to flush immediately; proxy_buffering off is the belt-and-suspenders equivalent for proxies that don't honour the header. (Caddy streams SSE correctly out of the box.)

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_PLUGIN_MANAGER=1
RAILBASE_TRUSTED_PROXIES=127.0.0.1/32
RAILBASE_LOG_FORMAT=json

Then systemctl enable --now railbase.

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 -> Administrators -> 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!