Run Scribase in your own cluster
This page is for a deployment outside hosted Scribase, under a written agreement (see /enterprise).
Scribase ships two first-class deploy targets under deploy/:
| Target | Path | Use it for |
|---|---|---|
| Docker Compose | deploy/compose/ |
Single-box deployments — one host, all services |
| Helm chart | deploy/helm/scribase/ |
Kubernetes — control plane and optional shared data plane |
Hosting many small projects (one per app) on one VPS? Use the single-box layout instead: scale-to-zero project containers, shared storage, realtime and functions, and built-in admin sign-in.
The service stack is the same in both: postgres · control-api · gateway (Envoy) · auth · rest · storage · realtime · functions · worker. Background jobs (backups, restores, verifications, exports, scheduled backups, retention, export expiry and usage metering) run inside control-api as its data-job workers, so there is no separate scheduler service.
What you must provide
Everything else has a working default. These four are yours to supply:
- A PostgreSQL 17 database. Compose runs one for you (
supabase/postgres); a production install points at your own managed database. The schema migrator needs a role withBYPASSRLS; the API and operator use least-privilege login roles. - A public domain — where clients reach the gateway, e.g.
scribase.example.com. - A TLS certificate for that domain. The gateway speaks plain HTTP internally and trusts a TLS terminator in front of it.
- Operator sign-in for the management API: either the control plane's own password login (
SCRIBASE_AUTH_MODE=builtin, no identity provider needed; see Built-in admin login), or an OIDC provider with token introspection (SCRIBASE_AUTH_MODE=oidc+SCRIBASE_OIDC_*). If you provision managed per-tenant databases via Neon, also a Neon API key (SCRIBASE_NEON_API_KEY).
Secrets are never baked into images or committed. Generate them, then pass them via .env (Compose) or a Kubernetes Secret / external secret store (Helm).
Single-box deploy (Docker Compose)
Files: deploy/compose/docker-compose.yml, .env.example, envoy/envoy.yaml, initdb/.
1. Configure
cd scribase/deploy/compose
cp .env.example .env
# Edit .env: set POSTGRES_PASSWORD, JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY,
# SECRET_KEY_BASE, and either SCRIBASE_AUTH_MODE=builtin or the
# SCRIBASE_OIDC_* values. See the file comments.Required secrets to generate:
# JWT secret (at least 32 random bytes, base64-encoded)
openssl rand -base64 32
# Anon key and service-role key are JWTs signed with JWT_SECRET.
# Generate them with the scribase CLI or the Scribase Console.2. Validate the config (no daemon required)
docker compose --env-file .env -f docker-compose.yml config3. Boot
docker compose --env-file .env -f docker-compose.yml up -d --buildBoot order is enforced by health and completion gates:
dbbecomes healthymigrate(one-shot) appliespostgres/migrations/*.sqland exits0control-api,auth,rest,storage,realtime,functionsstartgateway(Envoy) starts once all data-plane services are up
4. Verify
# Public API through the gateway
curl http://localhost:8000/health/ready
# Management API directly
curl http://localhost:8081/health/ready5. Register your first organization
export SCRIBASE_API_URL=http://localhost:8081
# builtin mode: the first run creates the administrator and signs in;
# later runs use `scribase login --email you@example.com`.
scribase admin setup --email you@example.com --setup-token scb_setup_...
# oidc mode instead: export SCRIBASE_ACCESS_TOKEN=<token from your identity provider>
scribase doctor
scribase org put myorg "My Organization"
scribase project put myorg myproject aws-us-east-1
scribase env create myorg myproject production production snapshot aws-us-east-1 init-001
scribase operation get myorg <operation-id>Kubernetes deploy (Helm)
The Helm chart at deploy/helm/scribase/ manages the control-plane components and its PostgreSQL dependency.
Prerequisites
- Helm 3.x
- A Kubernetes cluster (1.28+)
- A PostgreSQL 17 database accessible from the cluster
- A TLS-terminating Ingress controller
1. Add values
cp deploy/helm/scribase/values.yaml myvalues.yaml
# Edit myvalues.yamlKey values to set:
controlApi:
database:
url: "postgresql://scribase_api:PASSWORD@postgres:5432/scribase?sslmode=require"
oidc:
introspectionEndpoint: "https://idp.example.com/oauth/introspect"
issuer: "https://idp.example.com"
audience: "scribase-api"
clientId: "scribase-control"
clientSecret: "SECRET"
gateway:
domain: "scribase.example.com"
secrets:
jwtSecret: "GENERATED_JWT_SECRET"
anonKey: "GENERATED_ANON_JWT"
serviceRoleKey: "GENERATED_SERVICE_ROLE_JWT"2. Install
helm install scribase deploy/helm/scribase/ -f myvalues.yaml -n scribase --create-namespace3. Upgrade
helm upgrade scribase deploy/helm/scribase/ -f myvalues.yaml -n scribase4. Scale
kubectl scale deployment scribase-control-api --replicas=3 -n scribaseEnvironment variables reference
Control API
| Variable | Required | Description |
|---|---|---|
SCRIBASE_API_DATABASE_URL |
Yes | TLS URL for the scribase_api role |
SCRIBASE_AUTH_MODE |
No | oidc (default here) or builtin |
SCRIBASE_ADMIN_EMAIL, SCRIBASE_ADMIN_PASSWORD_HASH |
No | builtin only: pre-seed the first administrator |
SCRIBASE_OIDC_INTROSPECTION_ENDPOINT |
oidc only | RFC 7662 token introspection HTTPS endpoint |
SCRIBASE_OIDC_ISSUER |
oidc only | Exact trusted iss claim |
SCRIBASE_OIDC_AUDIENCE |
oidc only | Required aud claim |
SCRIBASE_OIDC_CLIENT_ID |
oidc only | Introspection client ID |
SCRIBASE_OIDC_CLIENT_SECRET |
oidc only | Introspection client secret |
SCRIBASE_HTTP_BIND |
No | Default 127.0.0.1:8080 |
SCRIBASE_TRUST_PROXY_TLS |
No | Set to 1 to allow non-loopback bind |
SCRIBASE_MAX_ENVIRONMENTS |
No | Default 10 per new organization |
SCRIBASE_DATABASE_MAX_CONNECTIONS |
No | Default 10 |
Organization single sign-on (optional)
Needed only if organizations will connect their own identity provider (see
Single sign-on). Apply control-plane migration 0018
first.
| Variable | Description |
|---|---|
SCRIBASE_PUBLIC_API_URL |
Public https:// origin of the control API. Used for the OIDC redirect URI and the SAML entity ID and ACS URL. SSO is unavailable until it is set. |
SCRIBASE_CONSOLE_URL |
Console origin the browser returns to after the IdP. Default https://console.scribase.com. |
SCRIBASE_KMS_MASTER_KEY |
64 hex characters. Seals OIDC client secrets at rest (shared with environment secrets). |
SCRIBASE_ORG_SETTINGS_DATABASE_URL |
Store for SSO settings, sign-in state and sessions; falls back to SCRIBASE_CONTROL_PLANE_ADMIN_DATABASE_URL. |
SCRIBASE_SSO_SESSION_HOURS |
Console session length after SSO, 1 to 168. Default 12. |
SCRIBASE_DOH_URL |
DNS-over-HTTPS JSON endpoint for domain verification. Default Cloudflare's public resolver. |
Neon integration (optional)
| Variable | Description |
|---|---|
SCRIBASE_NEON_API_KEY |
Neon API key for managed Neon environments |
SCRIBASE_NEON_PROJECT_REGION |
Default Neon region for new environments |
Database roles
Scribase uses three database roles:
| Role | Privileges | Used by |
|---|---|---|
scribase_migrator |
BYPASSRLS, DDL |
Schema migration runner |
scribase_api |
Least-privilege DML | control-api process |
scribase_operator |
Operator plans | Operator (worker) |
The bootstrap.sql script creates all three. The control-api process is never given the migrator or operator credential.
Upgrading
- Pull the new images or build from source.
- Run the migration runner:
docker compose run --rm migrate. - Restart services:
docker compose up -d.
The migration runner is idempotent and checksum-guarded — it will not re-apply already-applied migrations.