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
SP_ENCRYPTION_KEY (and any SP_ENCRYPTION_KEY_OLD)Your secret storeEvery stored credential becomes undecryptable. Keep retired keys until you have confirmed every credential is re-encrypted.
Encryption saltSP_DATA_DIR/.encryption_salt, or SP_ENCRYPTION_SALTSame as above when the key is a passphrase. A new random salt derives a different key.
Postgres databaseDATABASE_URLConnections, knowledge base, audit log, eval history, projects. Everything.
Session JWT secretSP_SESSION_JWT_SECRET, or the signalpilot-gateway-secrets volume in local modeLive notebook and sandbox sessions are invalidated.
Repositories and workspacesSP_REPOS_DIR, the workspace bucketUncommitted workspace state.
Object storageThe workspace, chat artifacts, and eval evidence bucketsChat files, dashboards, and eval transcripts beyond the retention window.

Schema changes on upgrade

The schema is managed with Alembic. At boot the gateway takes a Postgres advisory lock and upgrades the database to the latest revision, so several replicas starting at once do not race. You do not need to run anything by hand.

  • To migrate manually, run uv run alembic upgrade head from signalpilot/gateway/ with DATABASE_URL set. The gateway will find nothing left to do when it boots.
  • Pre-Alembic databases are handled once by a legacy bootstrap that brings the schema up to the first tracked revision, then stamps it. After that, Alembic owns the sequence.
  • Take a backup before upgrading. Roll back by restoring the database, not by starting an older image against a newer schema.

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 sandboxesSnapshotted and destroyed after SP_NOTEBOOK_IDLE_SNAPSHOT_SECONDS (15 min); snapshots resumable for SP_NOTEBOOK_SNAPSHOT_EXPIRATION_SECONDS (7 days)
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, snapshot idle sandboxes, and enforce eval retention. The chat worker is a separate process (gateway-chat-worker) that claims and runs chat and agent jobs; run at least one.

Health and metrics

  • GET /health returns {"status":"healthy"} and checks only that the process is up. Treat it as a liveness probe.
  • For readiness, probe a cheap authenticated route that exercises the database, such as GET /api/connections.
  • GET /api/metrics is an authenticated server-sent event stream, not a Prometheus endpoint, capped at 20 concurrent streams. Scrape-based monitoring needs an adapter.

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 local-file 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 the literal default.
  • Know what you are running. Besides the web UI, gateway, and Postgres, the stack starts a chat worker, a notebook container, a sandbox manager, MinIO with a bucket-creation job, Mailpit, and a read-only object proxy on a separate eval network. Every published port is bound to loopback.
  • Upgrades restart containers. docker compose up -d on one node recreates changed containers with a few seconds of downtime. Recreate the gateway and the chat worker together; they share an image.

See Run in production for the hardened path.