Running slonbase in production
#Reverse proxy
slonbase speaks plain HTTP. Put it behind a reverse proxy that terminates TLS, keep it listening
on 127. (the default), and set the app URL (meta.) to the public address, since
email links use it.
Two things matter for the proxy:
- Realtime uses Server-Sent Events (
/), which are long-lived responses. The proxy must not buffer them, and its read timeout must be longer than slonbase's 30 second keep-alive. slonbase sendsapi/ realtime X-Accel-Buffering: no, which nginx honors. - Uploads: raise the proxy's body size limit to your largest file field.
slonbase compresses its JSON answers itself (gzip, never the ones that carry tokens;
see compression), so leave compression off for / at the
proxy, or at least don't compress responses slonbase sent uncompressed: a proxy that gzips
sign-in answers undoes that protection.
Caddy handles both by default:
app.example.com {
reverse_proxy 127.0.0.1:8090
}nginx:
server {
listen 443 ssl;
http2 on;
server_name app.example.com;
# ssl_certificate ...; ssl_certificate_key ...;
client_max_body_size 100m;
location / {
proxy_pass http://127.0.0.1:8090;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off; # realtime
proxy_read_timeout 360s;
}
}Then tell slonbase which header has the client's address: in the dashboard's settings (Trusted proxy),
or {. Without it, logs, rate
limits and superuserIPs see the proxy's address. Only set it when slonbase can't be reached
except through the proxy, or clients can send any address they like.
#systemd
The ./. packages install slonbase.. It runs as the slonbase user and is
sandboxed: the file system is read-only except / and /, there are
no capabilities, and it has a private /.
sudo systemctl status slonbase
sudo journalctl -u slonbase -f
sudoedit /etc/default/slonbase && sudo systemctl restart slonbase # SLONBASE_BIND, RUST_LOG, ...Any SLONBASE_* variable from configuration can go in
/. To change the flags in ExecStart, use a drop-in
(sudo systemctl edit slonbase) rather than editing the unit, which upgrades replace.
On a stop or restart, slonbase finishes requests in flight for up to 5 seconds. Realtime clients are disconnected; the SDKs reconnect and resubscribe on their own.
#Docker
The image runs as an unprivileged user (uid 65532) and writes only to /. Docker volumes
keep that ownership. A disk or bind mount owned by root doesn't: slonbase starts, but file
uploads fail with "Permission denied". chown 65532:65532 the directory, or run the container as
root, as the platform setups do. Its health check
(slonbase health) fails while the server starts or can't reach the database. Pin a version tag
(ghcr.) rather than latest in production.
#PostgreSQL
- Version: 14 or newer. CI runs on 14 and 17.
- Role: slonbase needs a role that can create schemas in its database (
CREATEon the database), such as the database owner. It doesn't need to be a PostgreSQL superuser, and shouldn't be one: view collections' queries and the SQL console run with its privileges (see the security review). - Connections: each instance uses up to
--database-max-connections(16) plus two forLISTEN/, and up toNOTIFY projects.(4) plus one more for each project. Poolers must run in session mode, since transaction mode can't carrymax_connections LISTEN. - Managed services: use
sslmode=require, and--database-ca-filewhen the provider has its own CA. - Durability vs. write latency: with PostgreSQL's default
synchronous_commit = on, every write waits for the WAL to reach the disk.ALTER DATABASE slonbase SET synchronous_commit = offmakes single writes faster still (see the benchmarks). A crash can then lose the last fraction of a second of commits, but never corrupts data. (Embedded SQLite backends make the same trade-off by default.) - Your data is ordinary tables in the
slonbaseschema, so you can query them, replicate them, and back them up with the usual tools. Change their structure only through slonbase (the dashboard or the collections API), so the collection definitions stay in sync.
#Several instances
Any number of slonbase instances can share one database behind a load balancer. Realtime events,
collection changes, settings and backup restores reach every instance through PostgreSQL
LISTEN/. Cron jobs (including automatic backups) run on one instance per scheduled
minute, and migrations run once.
What's per instance:
- Files: use S3 storage (
s3in the settings), or all instances must share the same storage directory. - JavaScript hooks: deploy the same hook files to every instance.
- The encryption key, if you use one: every instance needs the same.
No client affinity needed. A realtime client's event stream lives on one instance, and the
SDKs set its subscriptions with a separate POST /. When that request reaches
another instance, it's forwarded through PostgreSQL to the one holding the stream, which checks
the request's auth and answers. So a plain round-robin load balancer works. (Affinity, like
nginx ip_hash, still saves that hop.)
Upgrade every instance together. Since 1.7, instances learn of new realtime events by reading the outbox from a snapshot, and no longer by a notification sent with each write; instances older than 1.7 wait for those notifications, so they'd miss events written through a 1.7 instance. Stop the old instances, start the new ones (the first runs the migration), as for any upgrade with a migration.
Rate limits are shared. While an instance is alone, it counts requests in memory. When
others share the database, the counters move to an unlogged PostgreSQL table, so a client gets
the configured limit across all instances, not that limit on each. Instances find each other
with a heartbeat every 20 seconds in _slonbase..
#Backups
There are two complementary ways to back up:
- slonbase backups (dashboard → Backups,
/, orapi/ backups backups.) are zip files with every collection, the settings and the local files. They're portable between servers and PostgreSQL versions, and are restored from the dashboard or withcron POST /. Keep them off the server withapi/ backups/ { key}/ restore backups., and encrypt them with a backups encryption key when they leave it.s3 - PostgreSQL backups (
pg_dump,pg_basebackup, WAL archiving, or your provider's snapshots) cover the whole database, with point-in-time recovery if you need it. Back up the storage directory (or bucket) alongside them.
Test a restore before you need one. Restoring a slonbase backup replaces every collection and the settings on every instance, and signs everyone out, since the signing key is restored too.
#Monitoring
GET /answersapi/ health 200when the server is ready (with"setupRequired": truewhile it waits for the setup wizard), and503while it starts or while the database is unreachable (the database check is shared for a second, so an outage shows within one, and a load balancer probing often costs at most a query a second).slonbase healthchecks the local server and is meant for container health checks.Errors and warnings go to stderr (the journal or
docker logs), and are also stored in the request logs (dashboard → Logs, level ≥ 4).GET /gives requests per hour.api/ logs/ stats Prometheus metrics at
/on a separate address, withmetrics --metrics-bind 127.(or0. 0. 1:9090 SLONBASE_METRICS_BIND). The endpoint isn't authenticated, so bind it to a private interface. It has:slonbase_http_requests_total{andproject, method, route, status} slonbase_http_request_duration_seconds(a histogram), by route pattern (/), so the series stay few;api/ collections/ { collection}/ records slonbase_db_connections{andstate="idle|in_use|max"} slonbase_db_waiting(requests waiting for a pooled connection: a sign the pool is too small);slonbase_realtime_clients,slonbase_realtime_events_total,slonbase_instances;slonbase_info{andversion} process_start_time_seconds.
# prometheus.yml scrape_configs: - job_name: slonbase static_configs: - targets: ["10.0.0.11:9090", "10.0.0.12:9090"]
#Security checklist
- Serve over HTTPS, and keep slonbase on
127.or a private network.0. 0. 1 - Set
trustedProxywhen, and only when, every request goes through your proxy. - Restrict superusers to known addresses with
superuserIPs, and turn on MFA for the_superuserscollection. - Leave the SQL console off unless you need it. It runs any SQL with the database role's
privileges, and a PostgreSQL superuser role can read server files or run programs
(
COPY .). When it's on, it only reads (one statement at a time, in a read-only transaction, at most 5,000 rows, a 30 second timeout) unless you also start slonbase with. . PROGRAM --sql-console-writes; leave that off in production, or give slonbase a role that isn't a PostgreSQL superuser. Creating or changing view collections also requires it, because their queries run with the role's privileges. - Review API rules:
nullallows superusers only, and""allows anyone. - Keep rate limits on (new installations start with them on), and turn them on for installations from before 0.4.2.
- Keep the config file private (
0600, which the wizard sets), since it contains the database password. - Set an encryption key (
SLONBASE_ENCRYPTION_KEY), so the settings' secrets and the token signing secret aren't readable in the database, its dumps and its backups. Keep it apart from them. - Report vulnerabilities as described in SECURITY.md.