scribasedocs

Your own cluster (by agreement)

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:

  1. 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 with BYPASSRLS; the API and operator use least-privilege login roles.
  2. A public domain — where clients reach the gateway, e.g. scribase.example.com.
  3. A TLS certificate for that domain. The gateway speaks plain HTTP internally and trusts a TLS terminator in front of it.
  4. 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

Terminal
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:

Terminal
# 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)

Terminal
docker compose --env-file .env -f docker-compose.yml config

3. Boot

Terminal
docker compose --env-file .env -f docker-compose.yml up -d --build

Boot order is enforced by health and completion gates:

  1. db becomes healthy
  2. migrate (one-shot) applies postgres/migrations/*.sql and exits 0
  3. control-api, auth, rest, storage, realtime, functions start
  4. gateway (Envoy) starts once all data-plane services are up

4. Verify

Terminal
# Public API through the gateway
curl http://localhost:8000/health/ready

# Management API directly
curl http://localhost:8081/health/ready

5. Register your first organization

Terminal
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

Terminal
cp deploy/helm/scribase/values.yaml myvalues.yaml
# Edit myvalues.yaml

Key values to set:

YAML
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

Terminal
helm install scribase deploy/helm/scribase/ -f myvalues.yaml -n scribase --create-namespace

3. Upgrade

Terminal
helm upgrade scribase deploy/helm/scribase/ -f myvalues.yaml -n scribase

4. Scale

Terminal
kubectl scale deployment scribase-control-api --replicas=3 -n scribase

Environment 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

  1. Pull the new images or build from source.
  2. Run the migration runner: docker compose run --rm migrate.
  3. Restart services: docker compose up -d.

The migration runner is idempotent and checksum-guarded — it will not re-apply already-applied migrations.