Operations
Production operator
Running the control plane, BYOC, and the least-privilege role model.
scribase-operator run is the production worker loop. Each cycle claims an
operation in PostgreSQL, commits the lease, performs at most one real Neon or
Kubernetes action, then opens a new fenced checkpoint transaction. An idle
worker polls at a bounded interval. Control-plane failures use bounded
exponential backoff and terminate the process after a configurable consecutive
failure threshold so the orchestrator can surface an unhealthy deployment.
Provider failures do not crash the worker because the reconciler has already
persisted their retry or terminal checkpoint.
scribase-operator reconcile-once executes the same production path for a single
cycle and remains useful for Kubernetes Jobs and diagnostics.
Before claiming work, each invocation atomically schedules at most one expired
preview in the selected organization. Candidate selection uses
FOR UPDATE SKIP LOCKED; the same transaction creates the delete operation,
cancels superseded work, advances desired state, and records the expiry audit
event. A second invocation cannot schedule the preview again once its phase is
deleting.
Trusted gateway or telemetry collectors renew an active preview with:
scribase-operator observe-activity acme billing pr-42
The optional fourth argument is the observed Unix epoch in seconds, which lets
a collector forward event time instead of process time. Collectors should
coalesce events (for example, one renewal per active preview every five minutes)
rather than emit one database write per request. The transaction accepts only a
newer event and rejects timestamps more than five minutes ahead of PostgreSQL
time. Missing, stale, non-preview, deleting, and deleted targets are safe
no-ops. Each accepted renewal is attributed to system:gateway-activity in the
immutable audit log. This command needs only the operator database URL and
psql; it does not initialize Neon or Kubernetes clients.
Required configuration:
| Variable | Purpose |
|---|---|
SCRIBASE_WORKER_ID |
Stable worker identity used in the lease fence |
SCRIBASE_OPERATOR_DATABASE_URL |
TLS URL for scribase_operator and provider resource mappings |
SCRIBASE_NEON_API_KEY |
Neon v2 API credential |
SCRIBASE_PUBLIC_DOMAIN |
Parent DNS name for environment gateways |
SCRIBASE_STORAGE_ENDPOINT |
S3-compatible Storage endpoint |
SCRIBASE_STORAGE_BUCKET |
S3-compatible Storage bucket |
SCRIBASE_STORAGE_REGION |
S3-compatible Storage region |
SCRIBASE_EXTERNAL_SECRET_STORE |
External Secrets Operator store name |
SCRIBASE_EXTERNAL_SECRET_PREFIX |
Remote path prefix owned by Scribase environments |
SCRIBASE_RUNTIME_IMAGE_GATEWAY |
Envoy image pinned as repository@sha256:<64 hex> |
SCRIBASE_RUNTIME_IMAGE_AUTH |
Supabase Auth image pinned by digest |
SCRIBASE_RUNTIME_IMAGE_REST |
PostgREST image pinned by digest |
SCRIBASE_RUNTIME_IMAGE_REALTIME |
Supabase Realtime image pinned by digest |
SCRIBASE_RUNTIME_IMAGE_STORAGE |
Supabase Storage image pinned by digest |
One operator serves every organization. It discovers the organizations with
claimable work (a due operation or an expired preview) through the
identifier-only functions of migration 0021_operator_organization_queue.sql,
then claims each tenant's queue under that tenant's forced-RLS context. It
serves organizations round-robin: at most
SCRIBASE_OPERATOR_ORGANIZATION_BATCH (default 25) organizations per turn,
in id order behind a cursor that wraps, and at most
SCRIBASE_OPERATOR_OPERATIONS_PER_TURN (default 4) consecutive actions per
organization before moving on. The tenant schema sweep on start and
migrate-tenants run for every organization, and engine moves run for every
organization that has one queued. To run a dedicated operator for a single
tenant instead, set SCRIBASE_OPERATOR_SCOPE=organization and
SCRIBASE_ORGANIZATION_ID.
The database URL must explicitly contain sslmode=require. PostgreSQL 16+
psql, curl, jq, and kubectl must be installed in the operator image.
External Secrets Operator and the configured SecretStore or
ClusterSecretStore must already exist. Scribase server-side-applies two
environment-scoped external-secrets.io/v1 resources, waits for their Ready
conditions, then deploys workloads whose required secretKeyRef entries provide
the final key-level readiness check. The operator service account has no Secret
read permission, and Scribase never places secret values in desired-state records
or Kubernetes manifests.
Every runtime image is required at startup and must use an immutable SHA-256
digest. Mutable tags from versions.lock.toml identify qualification inputs but
are not accepted by the production operator; the release pipeline must resolve
and record their registry digests before deployment.
For organization acme, project billing, and environment pr-42, the two
remote objects are:
<prefix>/acme/billing/pr-42/runtime
<prefix>/acme/billing/pr-42/database
The runtime object must expose ANON_KEY, DB_ENC_KEY, JWT_SECRET,
S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, SECRET_KEY_BASE, and
SERVICE_ROLE_KEY. The database object must expose AUTH_DATABASE_URL,
REALTIME_DB_HOST, REALTIME_DB_NAME, REALTIME_DB_PASSWORD,
REALTIME_DB_PORT, REALTIME_DB_USER, REST_DATABASE_URL, and
STORAGE_DATABASE_URL. The secret-manager provisioning workflow must set the
matching database role passwords before it publishes these values.
Optional configuration:
| Variable | Default |
|---|---|
SCRIBASE_LEASE_SECONDS |
60 |
SCRIBASE_DATABASE_MAX_CONNECTIONS |
10 |
SCRIBASE_NEON_API_BASE |
https://console.neon.tech/api/v2 |
SCRIBASE_NEON_DATABASE |
neondb |
SCRIBASE_NEON_ADMIN_ROLE |
neondb_owner |
SCRIBASE_GATEWAY_NAMESPACE |
scribase-system |
SCRIBASE_GATEWAY_NAME |
scribase-gateway |
SCRIBASE_KUBERNETES_TIMEOUT_SECONDS |
30 |
SCRIBASE_KUBERNETES_CONTEXT |
current context |
SCRIBASE_EXTERNAL_SECRET_STORE_KIND |
ClusterSecretStore; may be SecretStore |
SCRIBASE_EXTERNAL_SECRET_REFRESH_SECONDS |
300 (accepted range 10–86400) |
SCRIBASE_POLL_INTERVAL_MILLISECONDS |
1000 (accepted range 50–60000) |
SCRIBASE_MAX_CONSECUTIVE_ERRORS |
20 (accepted range 1–10000) |
SCRIBASE_ERROR_BACKOFF_MAX_SECONDS |
30 (accepted range 1–300) |
SCRIBASE_RECLAIM_INTERVAL_SECONDS |
300 (accepted range 10–86400): how often idle cycles delete the engine projects (for example the managed Neon project) of deleted Scribase projects, from scribase.released_provider_resources (discovered through migration 0022_operator_released_resources.sql). Failed deletions stay queued for the next pass |
SCRIBASE_PSQL_BINARY |
psql |
SCRIBASE_KUBECTL_BINARY |
kubectl |
The explicit self-check command is isolated verification only. It is the only
operator command that uses in-memory fake providers and must not be used as a
deployment entry point.