Data & multi-tenancy
The single-file vault store, collections, and the file-per-tenant model.
Updated
Railbase has no external database. All of your data lives in a single encrypted file managed by an in-process engine. This keeps operations trivial: one process, one file to back up.
The vault
The store is an encrypted, MVCC document store kept in one file —
pb_data/railbase.vault by default. "MVCC" means readers never block writers, so
the single-file model still serves concurrent traffic well. The file is encrypted
at rest with a master key (pb_data/.secret) and unlocked at boot with your vault
password (see Installation and Security).
What this means for you:
- No connection string, no DB server, no migrations service. Schema migrations apply automatically on boot.
- Backups are a file copy. A snapshot is a byte-exact copy of the
.vaultfile — see Backups & restore. - Keep it on local disk. Use the VPS's local disk, not a network mount, for correct file locking and durability.
- A single document can be up to 4 MiB. Documents larger than a storage page are split across pages transparently; past 4 MiB a write is rejected with an explicit error. Store large payloads as files, not document fields. (The REST create endpoint additionally caps a request body at 1 MiB.)
Collections
Data is organized into collections (think tables). The core owns the system collections (users, sessions, audit log, settings); each plugin owns its own, namespaced by slug (e.g. an inventory plugin's collections are prefixed so they never collide with another plugin's).
When you uninstall a plugin its collections are left dormant rather than deleted, so reinstalling restores your data. A separate, backup-gated purge permanently removes them — see Installing plugins.
Multi-tenancy
For deployments that serve multiple isolated organizations, Railbase uses
row-level tenancy inside the one vault file. Tenants are registered in the
built-in _tenants collection — from the CLI:
./railbase tenant create acme
./railbase tenant list
A collection opts into tenancy with the .Tenant() mixin in the
schema. Its rows then carry a tenant_id column, and the scope comes
from the request: a call with an X-Tenant: <tenant-uuid> header (the
TypeScript SDK's rb.setTenant(id)) writes and reads only
that tenant's rows — the value is stamped on insert and filtered on read.
Requests without the header operate at site scope. Roles can likewise be
granted per tenant (RBAC).
Tip
A single-tenant deployment is just the default case: no tenant setup, no
X-Tenant header, no .Tenant() mixins required. Reach for multi-tenancy
only when you actually host separate organizations on one instance. Note that
backups operate on the whole vault file — i.e. on all tenants at once.