Skip to main content

Operations

What a self-hosted deployment needs after it is running: what to back up, what happens on upgrade, what grows without bound, and what the background loops are doing.

Cloud customers can skip this page — it is all managed.

Back this up

Losing any of these costs you data. Losing the first two costs you data you cannot reconstruct.

WhatWhereIf you lose it
Encryption saltSP_DATA_DIR/.encryption_saltEvery stored credential becomes undecryptable. The salt derives the key from your passphrase; a new random salt derives a different key.
SP_ENCRYPTION_KEY (and any SP_ENCRYPTION_KEY_OLD)Your secret storeSame as above. Keep retired keys until you have confirmed every credential is re-encrypted.
PostgreSQL databaseDATABASE_URLConnections, knowledge base, audit log, eval history, projects — everything.
Session JWT secretSP_SESSION_JWT_SECRET, or the gateway-private secrets volume in local modeLive notebook and sandbox sessions are invalidated.
RepositoriesSP_REPOS_DIRUncommitted workspace state.
Eval evidence bucketSP_EVAL_S3_BUCKETTranscripts and captures beyond the retention window.

:::danger Test the restore An encryption salt backed up but never restored is a backup you do not have. Restore into a scratch environment and confirm a connection still works before you need it for real. :::

Schema changes on upgrade

There is no Alembic and no migration command. At boot the gateway runs create_all to add missing tables, then a series of idempotent _ensure_* steps that add columns, backfill defaults, and create indexes that create_all cannot express.

What this means in practice:

  • Upgrades are forward-only. There is no down-migration. Roll back by restoring the database, not by starting an older image against a newer schema.
  • Some steps are destructive by design. They deduplicate rows and drop columns that must not survive (a plaintext notebook token, for example, which also force-stops live notebook sessions).
  • Take a backup before upgrading. The one manual migration in the codebase says so in its own header, and gates cloud /health behind a 503 until it has run.

Retention and growth

Some things are pruned automatically. The important one is not.

DataRetention
Knowledge retrieval events90 days
Connection-health events7 days
Eval artifactsLast 10 runs per workspace
Eval transcriptsLast 100 runs per workspace
Eval accuracy historyNever pruned (small)
Idle notebook sessionsReaped after SP_NOTEBOOK_IDLE_TIMEOUT (2 h)
Audit logNever pruned
Chat traces, analysis trailsNever pruned

audit_retention_days in the plan table is a cloud plan attribute; it does not prune a self-hosted gateway_audit_logs. Schedule your own deletion to match your policy, and keep an export first if you need the history:

-- keep 400 days
DELETE FROM gateway_audit_logs
WHERE timestamp < EXTRACT(EPOCH FROM now() - INTERVAL '400 days');

Background loops

Each gateway process runs about a dozen asyncio loops. There is no leader election — every replica runs all of them. Two are worth knowing about before you scale out:

  • Health ping opens a real connection to every connection in every workspace every 30 seconds. With many workspaces and a small database pool this is real load; it is also what makes the health page useful.
  • Eval reaper deletes eval-* branch databases whose run lease has expired, matching across the whole server rather than per workspace. Give eval branches a dedicated Postgres server — see Deploying the eval harness.

Others sweep pools, refresh schemas, prune knowledge retrievals, run schema watches, reap notebooks, and enforce eval retention.

Health and metrics

  • GET /health returns {"status":"healthy"} and checks nothing — not the database, not the pools. It is a liveness probe, not a readiness probe. There is no /readyz or /livez.
  • GET /api/metrics is an authenticated SSE stream, not a Prometheus endpoint, capped at 20 concurrent streams. Scrape-based monitoring needs an adapter.

For readiness, probe something that actually exercises the database — a cheap authenticated route such as GET /api/connections — rather than /health.

Fixed ceilings

Not configurable, worth knowing:

LimitValue
Request body2 MiB
Concurrent eval runs2 per gateway process
Eval export archive256 MB, assembled in memory
Report HTML5 MB
Audit export rowsSP_MAX_EXPORT_ROWS, default 50,000
Open eval-set uploads3 per user
Concurrent sandboxes10
Concurrent metrics streams20

Compose stack hygiene

The bundled docker-compose.yml is a development stack. Before exposing it anywhere:

  • Replace SP_ENCRYPTION_KEY. The committed value is public.
  • Replace the database password. changeme_dev_only is not a joke setup; it is the literal default.
  • Decide which services you actually need — the stack also starts MinIO, Mailpit, a sandbox, a notebook server, and an object proxy.

See Self-hosting in production for the hardened path.