slonbase Docs v2.1.3
Get started

Configuration

slonbase has two kinds of configuration:

  • Server configuration covers what the process needs before it can reach the database: where to listen, the database connection, and local paths. It comes from flags, environment variables and a TOML file.
  • Application settings are stored in the database and shared by every instance: app name and URL, mail, S3, backups, logs, rate limits and so on. You change them in the dashboard (Settings) or with PATCH /api/settings, and they apply right away, without a restart.

#Server configuration

Values come from (highest priority first) command-line flags, environment variables, the config file, and then the defaults.

FlagEnvironment variableConfig fileDefault
--configSLONBASE_CONFIGslonbase.toml
--bindSLONBASE_BINDserver.bind127.0.0.1:8090
--metrics-bindSLONBASE_METRICS_BINDserver.metrics_bindnone (no metrics)
--http-ca-fileSLONBASE_HTTP_CA_FILEserver.http_ca_filenone
--database-urlSLONBASE_DATABASE_URLdatabase.urlnone (setup mode)
--database-max-connectionsSLONBASE_DATABASE_MAX_CONNECTIONSdatabase.max_connections16
--database-ca-fileSLONBASE_DATABASE_CA_FILEdatabase.ca_filenone
--storage-pathSLONBASE_STORAGE_PATHstorage.pathslonbase_data/storage
--backups-pathSLONBASE_BACKUPS_PATHbackups.pathslonbase_data/backups
--backups-encryption-keySLONBASE_BACKUPS_ENCRYPTION_KEYbackups.encryption_keynone (not encrypted)
--encryption-keySLONBASE_ENCRYPTION_KEYserver.encryption_keynone (settings not encrypted)
--sql-consoleSLONBASE_SQL_CONSOLEserver.sql_consoleoff
--sql-console-writesSLONBASE_SQL_CONSOLE_WRITESserver.sql_console_writesoff (the console only reads)
--no-compressionSLONBASE_NO_COMPRESSIONserver.compressionon (gzip for JSON)
--hooks-dirSLONBASE_HOOKS_DIRhooks.dirslonbase_hooks
--hooks-poolSLONBASE_HOOKS_POOLhooks.pool_size10
--hooks-watchSLONBASE_HOOKS_WATCHhooks.watchtrue
--migrations-dirSLONBASE_MIGRATIONS_DIRmigrations.dirslonbase_migrations
--automigrateSLONBASE_AUTOMIGRATEmigrations.automigratefalse
--projects-pathSLONBASE_PROJECTS_PATHprojects.pathslonbase_data/projects
projects.max_connections4
serve --setup-tokenSLONBASE_SETUP_TOKENrandom

Relative paths are relative to the working directory. The Docker image sets SLONBASE_CONFIG=/data/slonbase.toml and keeps the paths under /data. The packages pass --config /etc/slonbase/config.toml and paths under /var/lib/slonbase in the service file.

A config file with every option:

toml
[server]
bind = "127.0.0.1:8090"
sql_console = false
# sql_console_writes = false   # let the SQL console change data (INSERT, UPDATE, DELETE, DDL)
# compression = false   # don't gzip JSON answers (saves their CPU where bandwidth is free)
# metrics_bind = "127.0.0.1:9090"   # Prometheus metrics at /metrics (not authenticated)
# http_ca_file = "/etc/slonbase/private-ca.pem"   # extra CAs for S3 and OAuth2 providers
# encryption_key = "..."   # encrypts the settings in the database; prefer SLONBASE_ENCRYPTION_KEY

[database]
url = "postgres://slonbase:secret@127.0.0.1:5432/slonbase"
max_connections = 16
# ca_file = "/etc/ssl/certs/rds-global-bundle.pem"

[storage]
path = "slonbase_data/storage"

[backups]
path = "slonbase_data/backups"
# encryption_key = "..."   # encrypts new backups; prefer SLONBASE_BACKUPS_ENCRYPTION_KEY

[hooks]
dir = "slonbase_hooks"
pool_size = 10
watch = true

[migrations]          # see migrations.md
dir = "slonbase_migrations"
automigrate = false

[projects]            # see projects.md
path = "slonbase_data/projects"
max_connections = 4

The setup wizard writes database.url into this file with 0600 permissions, since it contains the password. When the URL comes from a flag or the environment, the wizard doesn't change it.

Database URLs use the libpq format, e.g. postgres://user:pass@host:5432/db?sslmode=require. Percent-encode special characters in the password. sslmode can be disable, prefer (the default) or require. TLS checks the server's certificate against the Mozilla root certificates; for providers with their own CA (such as Amazon RDS), pass it with --database-ca-file.

Connections: each instance opens up to max_connections to PostgreSQL, plus two for LISTEN (realtime and projects), and each project adds its own. With several instances, keep the total below the server's max_connections, or use a pooler in session mode. Transaction-mode poolers (PgBouncer pool_mode = transaction) don't support LISTEN, so slonbase needs a direct or session-mode connection.

Logging goes to stderr and is controlled by RUST_LOG (default info; warn, debug, and per-module filters such as info,slonbase=debug also work). Requests are logged to the database too; see logs.

SQL console (--sql-console): the dashboard's SQL page, for superusers. See security before you turn it on.

  • Read-only by default. It runs one statement at a time, and only reads: SELECT, WITH, SHOW, EXPLAIN, VALUES and TABLE, in a READ ONLY transaction. PostgreSQL itself refuses anything that would write, including a WITH ... DELETE or a function that writes.
  • Writes (--sql-console-writes, SLONBASE_SQL_CONSOLE_WRITES=true) let it run any statement or script (INSERT, UPDATE, DELETE, DDL), in one transaction that commits. Turn it on where you need it, not everywhere.
  • Limits: rows come through a cursor, 1,000 by default and at most 5,000 a query (16 MB of values); statements stop after 30 seconds, or 5 seconds waiting for a lock; queries are at most 20,000 characters; two run at a time per instance. Each query gets a connection of its own, closed afterwards, so nothing it sets outlives it.
  • Collections are tables in the slonbase schema, which the console searches first: SELECT * FROM tasks works as written.
  • Results can be exported from the dashboard as Excel (.xlsx), CSV, TSV or JSON: the rows the query returned, built in the browser.

#Encrypting the settings

The database holds secrets: the settings' (the SMTP password, the S3 secret keys), the secret every token is signed with (whoever reads it can sign in as anyone), the OAuth2 providers' client secrets in auth collections, and the passwords of projects' own database roles. With an encryption key, all of them are stored encrypted (AES-256-GCM), so a database dump, a read-only database role or a backup gives nothing away without the key.

sh
export SLONBASE_ENCRYPTION_KEY="$(openssl rand -hex 16)"   # 32 characters; keep it safe
slonbase serve
  • Existing installations are encrypted on the first start with a key (the settings, the signing secret, client secrets and project role passwords); tokens stay valid.
  • Every instance, and the slonbase commands you run against the database, need the same key. Projects use the main installation's.
  • Without the key, or with another one, slonbase doesn't start, and says why. A lost key can't be recovered: keep it in a password manager or your secrets store, apart from the database and its backups.
  • Backups carry them encrypted, and restore only where the key is the same; a restore that can't read them is refused and changes nothing. Restoring a backup made without a key encrypts its settings again.
  • Changing or removing the key: stop every instance, run slonbase rekey --to <new key> (or slonbase rekey with no --to, to store them unencrypted) with the current key set as usual, then start the instances with the new one. It covers the main installation and every project.
  • PocketBase: the format is PB_ENCRYPTION's, so with the same value as the key, an import brings PocketBase's encrypted settings along.

Records themselves aren't encrypted: protect them with the database's own access control, and encrypted disks and backups.

#Application settings

These are the sections of GET/PATCH /api/settings, and of the dashboard's Settings pages.

SectionWhat it controls
metaappName (the browser tab, the sidebar, the sign-in page, emails), appURL (email links, OAuth2 redirects), senderName, senderAddress, accentColor (a named accent such as ocean, or #rrggbb), hideControls, and the images logo, logoDark, favicon (ids from Settings → Branding)
smtpOutgoing mail: enabled, host, port, username, password, authMethod, tls, localName. While it's disabled, emails are written to the log.
s3File storage in an S3 bucket instead of --storage-path
backupscron (automatic backups), cronMaxKeep, and s3 (keep backups in a bucket)
logsmaxDays (0 turns request logs off), minLevel, logIP, logAuthId, and slonbase's excludePaths, slowRequestMs, maxRows
batchenabled, maxRequests, timeout, maxBodySize for /api/batch
rateLimitsenabled, rules
trustedProxyheaders carrying the client IP, useLeftmostIP
superuserIPsAddresses or ranges superusers may use
securityslonbase's: allowedOrigins (CORS; ["*"] by default), maxUploadMb (128), hsts, maintenance, maintenanceMessage, authEmailInterval (seconds between auth emails to an account, 60), loginLockout (attempts, minutes)
filesslonbase's: defaultMaxSize (bytes, for file fields without a size of their own; 0 is 5 MB), thumbs (off: the original file is served instead of a thumbnail)
loginslonbase's: the sign-in page's layout (split, centered, minimal), background (none, grid, asset) and backgroundAsset, title, subtitle, logo, notice, footerLinks (up to 3, https: or mailto:), showPoweredBy
mailLayoutslonbase's: enabled, html (a document with {CONTENT} where each email's body goes)

Roles and API keys are kept with the settings but only /api/team shows and changes them.

Emails fill {APP_NAME}, {APP_URL}, {TOKEN} (and {OTP}, {OTP_ID}, {ALERT_INFO} in their templates) as PocketBase does, and slonbase's {APP_LOGO} (the logo, or the name), {APP_LOGO_URL}, {ACCENT} and {RECORD:field} (the recipient's fields, escaped; hidden fields stay empty).

Lockout. With security.loginLockout.attempts above 0, an identity that fails that many password sign-ins from one address within minutes gets 429 until the window ends, even with the right password; a successful sign-in clears the count. It's counted in the database, so every instance shares it. POST /api/settings/rate-limits/explain ({method, path, audience}) says which rate-limit rule a request would meet.

CORS. Pages on any site may call the API, as with PocketBase (security.allowedOrigins is ["*"]). List origins (https://app.example.com) to allow only those; an empty list allows none, so browsers only call the API from its own address. Maintenance answers 503 with your message to everyone but superusers and /api/health.

Secrets (the SMTP password, S3 secrets, OAuth2 client secrets) are never returned by the API. POST /api/settings/test/email and POST /api/settings/test/s3 check the mail and S3 settings.

#File storage in S3

When s3.enabled is set, every instance stores new files in the bucket. You need this when you run several instances behind a load balancer.

sh
curl -X PATCH localhost:8090/api/settings -H "Authorization: $TOKEN" -H 'content-type: application/json' \
  -d '{"s3": {"enabled": true, "bucket": "my-files", "region": "eu-west-1",
       "endpoint": "https://s3.eu-west-1.amazonaws.com", "accessKey": "...", "secret": "...",
       "forcePathStyle": false}}'
curl -X POST localhost:8090/api/settings/test/s3 -H "Authorization: $TOKEN" -d '{}'   # 204 when it works

Any S3-compatible service works: AWS, MinIO, Cloudflare R2, Backblaze B2, Wasabi or DigitalOcean Spaces. Use forcePathStyle: true for MinIO and other servers addressed by IP or a single host name. Existing files aren't moved when you switch storage.

An S3 server with a certificate from a private CA (a MinIO on your network, say) needs that CA: pass its PEM file with --http-ca-file. It's trusted besides the usual public roots, for all outbound HTTPS (S3, and OAuth2 providers on the same network).

Downloads straight from the bucket. With s3.presignedDownloads on, a file request gets a redirect to a short-lived signed URL of the bucket instead of the file itself, so the bytes don't pass through slonbase. The checks stay in slonbase: the file must exist, a protected file still needs a valid file token, thumbnails are made first, and the download hook runs. The signed URL makes the bucket answer with the type, disposition and caching slonbase would have sent. Public files get a URL that stays the same for an hour, so browsers cache them; a protected file's URL lasts five minutes at most and isn't cached. Clients must be able to reach the S3 endpoint, so this suits a public endpoint (AWS, R2, ...) more than a MinIO on an internal network. It's off by default, and backups ignore it.

Large files (backups, mostly) are uploaded in 16 MiB parts above 64 MiB, so each part is retried on its own and objects can pass the 5 GiB a single upload allows. A failed upload is aborted, so no parts are left behind.

#Logs

Every request is logged at level 0, or level 8 with the error for failed requests. Dashboard files and successful health checks are skipped. slonbase's own warnings and errors are logged too. Logs are kept for logs.maxDays days (5 by default) in an unlogged PostgreSQL table, so writing them is cheap. They're written in batches in the background, and they don't survive a PostgreSQL crash. Browse them in the dashboard or with /api/logs. An entry's data is kept under logs.maxDataSize bytes (16 KiB when 0): a larger one keeps the attributes that fit and is marked "__pb_truncated__": true. Paths in logs.excludePaths aren't logged unless they fail with a server error; requests slower than logs.slowRequestMs are logged as warnings (and marked "slow": true) whatever minLevel; logs.maxRows caps the entries kept.

Turning request logging off (maxDays 0), as a bulk import often does, means traffic isn't counted: the overview says so, and Health warns.

#Rate limits

rateLimits limits requests per client IP. New installations (and new projects) start with them on and these rules: *:auth 2 requests per 3 seconds, *:create 20 per 5 seconds, /api/batch 3 per second and /api/ 300 per 10 seconds. Installations from before 0.4.2, and imported ones, start with them off. A rule's label is one of:

  • {collection}:{action} or *:{action}, where the action is list, view, create, update, delete, auth, authWithPassword, requestOTP, file, and so on;
  • METHOD /path;
  • a path prefix ending in /.

Each rule can apply only to @guest or @auth requests, and allows maxRequests per duration seconds per client IP. Over the limit, requests get 429. Superusers and excludedIPs aren't limited. A single instance counts in memory; with several on one database, the counters are shared through PostgreSQL, so the limits apply across instances.

#Trusted proxy and superuser IPs

Behind a reverse proxy, every request comes from the proxy's address. Set trustedProxy.headers (e.g. ["X-Forwarded-For"] or ["X-Real-IP"]) so logs, rate limits, auth alerts and superuserIPs see the client's address. By default the rightmost X-Forwarded-For address is used, the one your proxy added; useLeftmostIP takes the first one instead, which a client can forge unless every proxy in front overwrites the header. Only set these headers when slonbase can't be reached except through the proxy.

superuserIPs (e.g. ["10.0.0.0/8", "203.0.113.7"]): when set, superuser requests, superuser sign-in and the _superusers endpoints only work from those addresses.

#Batch requests

POST /api/batch (pb.createBatch() in the SDKs) runs record creates, updates, upserts and deletes in one transaction: all of them apply, or none, and the response carries the failing request's error. Enable it with batch.enabled. Each request uses the batch's auth and rules and runs the request hooks.

#Backups

Backups (/api/backups, or the dashboard's Backups page) are zip files containing every collection, the settings, and the local files (unless files are in S3). Restoring one replaces all of that in one transaction, on every instance. It also restores the installation's signing key, so tokens issued after the backup stop working.

Backups are kept in --backups-path, or in an S3 bucket (backups.s3). backups.cron (e.g. 0 3 * * *, UTC) creates them automatically, and backups.cronMaxKeep limits how many are kept. See deployment for how they fit with PostgreSQL's own backups.

Encryption. With a backups encryption key (SLONBASE_BACKUPS_ENCRYPTION_KEY, at least 12 characters), new backups are encrypted (AES-256-GCM, with a key derived from the passphrase), so a copy in a bucket or on a laptop reveals nothing without it. They keep their names, download encrypted, and restore on any installation with the same key; a plain backup uploaded there is stored encrypted. Restoring needs the key: keep it somewhere other than the backups (a password manager or your secrets store), because a lost key can't be recovered. The key lives in the configuration, never in the database, since the database is what's backed up. slonbase decrypt-backup <backup> <archive.zip> turns an encrypted backup back into its zip.

#Auth alerts

authAlert (per auth collection, on by default) emails a record when it signs in from an IP address and user agent it hasn't used before. It isn't sent on the first sign-in, nor on the first sign-in after a password change. The last 5 devices per record are remembered.