Skip to content

Storage backends

Nimbus selects its tenant storage backend with the --tenant-provider flag on nimbus start. Tenants stay isolated at the storage level on every backend, and clients connect the same way regardless of which one you pick — the choice is purely a deployment decision.

This page shows how to run each backend. For the full flag ↔ environment variable ↔ config-key table, see the configuration reference.

Backend--tenant-providerExternal service?Pick it when
SQLitesqlite (default)NoLocal development and single-node production with zero setup
PostgrespostgresYesYou already run PostgreSQL infrastructure
MySQLmysqlYesYou already run MySQL infrastructure
libSQL / Tursolibsql-replicaYesYou want local replica reads against a remote libSQL primary (typically Turso)
redbredbNoYou specifically need the retained embedded key-value backend; prefer SQLite otherwise

Nimbus provisions tenants through the selected backend before serving the request that needs them. Automatic tenant creation and protocol adapters await the complete provider lifecycle, including opening an already-existing durable tenant into the current process. A retry after an interrupted create therefore does not report success until the tenant runtime is ready.

Provider-reachable background work follows the same boundary. For example, each DynamoDB TTL sweep admits or opens its configured tenants sequentially before entering the synchronous sweep core; one tenant’s admission or sweep failure is reported without preventing later tenants in that pass.

The Rust Engine::create_tenant blocking API is embedded-only and accepts SQLite or redb compositions. Embedders using PostgreSQL, MySQL, or libSQL must retain an Arc<Engine> and use create_tenant_async for explicit creation or ensure_tenant_ready_async for idempotent request admission. Calling the blocking API on an external-provider composition fails immediately with an actionable error instead of surfacing later during traffic.

No flags needed — nimbus start uses embedded SQLite with one database file per tenant in the data directory (<data-dir>/<tenant-id>.sqlite3):

Terminal window
nimbus start --data-dir ./data
# Equivalent to: nimbus start --data-dir ./data --tenant-provider sqlite

The data directory defaults to ./data when --data-dir is omitted.

Point Nimbus at an existing PostgreSQL instance. Each tenant gets an isolated schema:

Terminal window
nimbus start \
--tenant-provider postgres \
--postgres-url "postgresql://user:pass@db.example.com:5432/nimbus"

--postgres-url is required. Optional flags:

  • --postgres-metadata-schema — cross-tenant metadata schema (default nimbus_provider).
  • --postgres-tenant-schema-prefix — per-tenant schema name prefix (default tenant_).
  • --postgres-min-connections / --postgres-max-connections — pool sizing; the pool’s built-in sizing applies when unset.

Point Nimbus at an existing MySQL instance. Each tenant gets an isolated database:

Terminal window
nimbus start \
--tenant-provider mysql \
--mysql-url "mysql://user:pass@db.example.com:3306/nimbus"

--mysql-url is required. Optional flags:

  • --mysql-metadata-database — cross-tenant metadata database (default nimbus_provider).
  • --mysql-tenant-database-prefix — per-tenant database name prefix (default tenant_).
  • --mysql-min-connections / --mysql-max-connections — pool sizing; the pool’s built-in sizing applies when unset.

Nimbus samples MySQL’s global max_allowed_packet when the provider starts. If --mysql-url also sets the driver’s client-side max_allowed_packet query parameter, Nimbus uses the lower of the client and server limits. Durable journal batches are split into ordered statements before either ceiling is crossed. Restart Nimbus after changing the global packet limit so new provider connections and the journal planner use the same value.

Replica-connected SQLite: Nimbus keeps a local replica cache and connects to a remote libSQL primary. Each tenant gets an isolated namespace:

Terminal window
nimbus start \
--tenant-provider libsql-replica \
--libsql-url "libsql://your-db.turso.io" \
--libsql-auth-token "<token>" \
--libsql-admin-url "https://admin.your-db.example.com" \
--libsql-replica-cache-dir ./cache

Three flags are required: --libsql-url (the primary), --libsql-admin-url (the admin API Nimbus uses to provision tenant namespaces), and --libsql-replica-cache-dir (the local replica cache root). Optional flags:

  • --libsql-auth-token — auth token for the primary.
  • --libsql-admin-auth-headerAuthorization header value for the admin API.
  • --libsql-metadata-namespace — metadata namespace (default nimbus_provider).
  • --libsql-tenant-namespace-prefix — per-tenant namespace prefix (default tenant_).

The retained embedded key-value backend. One database file per tenant in the data directory (<data-dir>/<tenant-id>.redb):

Terminal window
nimbus start --tenant-provider redb --data-dir ./data

Prefer SQLite for new deployments unless you have a specific reason to use redb.

Every flag above has a NIMBUS_* environment variable and a JSON config-file key. Precedence is CLI flag, then environment variable, then config file:

Terminal window
export NIMBUS_TENANT_PROVIDER=postgres
export NIMBUS_POSTGRES_URL="postgresql://user:pass@db.example.com:5432/nimbus"
nimbus start

Pass a JSON config file with --config <path> or NIMBUS_CONFIG=<path>. Storage settings live under the persistence key:

{
"persistence": {
"tenant_provider": "postgres",
"postgres_url": "postgresql://user:pass@db.example.com:5432/nimbus"
}
}

See the configuration reference for every key. Note that flags for a backend you have not selected are rejected at startup — for example, --postgres-url without --tenant-provider postgres is an error.

Tenants never share storage, whichever backend is active:

BackendIsolation boundary
SQLiteDatabase file per tenant
PostgresSchema per tenant
MySQLDatabase per tenant
libSQL / TursoNamespace per tenant
redbDatabase file per tenant

For the isolation model itself, see tenant isolation and the concept page.

Cross-tenant control-plane data is stored in a separate embedded redb database, nimbus-control.db, inside the control data directory. That directory defaults to the data directory; override it with --control-data-dir or NIMBUS_CONTROL_DATA_DIR. The control plane stays local even when tenant data lives in Postgres, MySQL, or libSQL.

Nimbus can encrypt the local files it owns — embedded SQLite and redb tenant databases, the control-plane database, and libSQL replica caches. With Postgres or MySQL, tenant data lives in the external database and its encryption is the database’s responsibility; Nimbus still encrypts the local control plane. See encryption at rest.