scribasedocs

Getting Started

Single-box deploy (many projects)

This layout hosts 30 to 50 small projects (for example one per mobile app or game) on one VPS in a 6 to 8 GB RAM budget. It is the layout Scribase itself is operated on. For a plain one-tenant stack, see Self-hosting. The full operator reference, with memory budgets and every overlay, is DEPLOY.md in the repository.

File Role
deploy/compose/docker-compose.yml Base stack: control-plane Postgres, migrator, control API
deploy/compose/docker-compose.single-box.yml Overlay: operator, activator, project router, restore-verify scratch database, memory caps
deploy/compose/docker-compose.shared-services.yml One shared storage, realtime and edge-functions runtime for every project
deploy/compose/docker-compose.postgres-cow.yml Default engine: one small Postgres 17 per environment on this box, branches as copy-on-write clones
deploy/compose/docker-compose.neon-local.yml Optional: self-hosted Neon on the same box (instead of postgres-cow)
deploy/compose/single-box.env.example Env template (variable names only, no values)

How it works

  • Every environment gets its own Postgres 17 on the box (SCRIBASE_DATA_ENGINE=postgres-cow). A branch (preview, staging, a sandbox per agent session) is a copy-on-write clone of its parent's data directory: it takes milliseconds and only costs the blocks that later change. An idle environment's database is stopped after SCRIBASE_POSTGRES_COW_SUSPEND_SECONDS (default 900, like the API container) and started again by the next connection. The bundled db holds only the control plane. No cloud account is needed.
  • Managed Neon is optional. Set SCRIBASE_NEON_API_KEY and new projects can use neon-cloud (one Neon project per Scribase project, 0.25 CU, suspended after 5 idle minutes, point-in-time restore). scribase project move <org> <project> --engine postgres-cow|neon-cloud|neon-local moves a project between engines and keeps its URL and keys.
  • One container per environment runs GoTrue (/auth/v1) and PostgREST (/rest/v1). It scales to zero: the activator starts it on the first request and stops it after SCRIBASE_PROJECT_IDLE_SECONDS (default 900) without traffic. An idle project uses disk, not memory.
  • Storage, realtime and edge functions are shared, multi-tenant services. Each function call runs in its own isolated Deno worker that only sees its environment's variables and secrets.
  • Every environment is served at https://<host label>.<your domain> through the project router.
  • Operator sign-in is built in (SCRIBASE_AUTH_MODE=builtin). No identity provider is needed. See Built-in admin login.

1. Build the images

Terminal
sudo git clone <your scribase remote> /srv/src/scribase
cd /srv/src/scribase/deploy/compose

# Control-plane image (control API, operator, activator, CLI).
docker compose -f docker-compose.yml build migrate
# Per-environment runtime image (GoTrue + PostgREST).
docker compose -f docker-compose.yml -f docker-compose.single-box.yml \
  --profile build build project-runtime-image

2. Configure

Terminal
sudo install -m 640 -o root -g deploy single-box.env.example /etc/products/scribase.env
sudoedit /etc/products/scribase.env

Fill every blank in the template:

  • Control-plane passwords and keys (openssl rand -hex 32 for each).
  • SCRIBASE_ORGANIZATION_ID, the id of your first organization.
  • SCRIBASE_POSTGRES_COW_HOST_DIR (default /srv/scribase/pg-cow): a directory on a filesystem that can clone files (XFS, btrfs or ZFS), owned by uid 10001, mode 0700. On an ext4 root, create an XFS image file and loop-mount it there (see Copy-on-write storage).
  • Optional: SCRIBASE_NEON_API_KEY, a Neon API key, to offer neon-cloud too.
  • The SHARED_* keys for the shared storage and realtime services, and SMTP.
  • SCRIBASE_FUNCTIONS_ADMIN_KEY, its own random value. The functions runtime refuses an admin key that equals a key functions can read.
  • SCRIBASE_RESTORE_VERIFY_DB_PASSWORD, the password of the bundled restore-verify-db scratch server that Backup Verify restores into. Leave SCRIBASE_RESTORE_VERIFY_DATABASE_URL blank to use that server. Without a verification database, Backup Verify answers 503 verification_not_configured.
  • Leave SCRIBASE_ADMIN_EMAIL and SCRIBASE_ADMIN_PASSWORD_HASH blank to get a one-time setup token in the log, or pre-seed the administrator (see Built-in admin login).

Keep every published port on 127.0.0.1. Docker bypasses host firewalls such as UFW.

3. DNS and TLS

  • api.<domain> and console.<domain>: ordinary records pointing at the box.
  • *.<domain>: one wildcard record to the box. Every environment is served at https://<host label>.<domain>.
  • A wildcard certificate. HTTP-01 cannot issue one, so use DNS-01 or, behind Cloudflare, a Cloudflare Origin CA certificate for *.<domain>, <domain> with SSL mode "Full (strict)".
  • Your TLS terminator forwards api. to 127.0.0.1:8081 and *. to 127.0.0.1:8010 (the project router).

4. Boot

Terminal
cd /srv/src/scribase/deploy/compose
dc="docker compose -p scribase --env-file /etc/products/scribase.env \
  -f docker-compose.yml -f docker-compose.single-box.yml -f docker-compose.shared-services.yml \
  -f docker-compose.postgres-cow.yml"

$dc config -q      # validate first
$dc up -d

5. Create the administrator and the organization

Skip admin setup if you pre-seeded SCRIBASE_ADMIN_*, and sign in with scribase login --email ... instead.

Terminal
$dc logs control-api | grep -A1 'setup token'          # scb_setup_...
$dc exec -e SCRIBASE_API_URL=http://127.0.0.1:8080 control-api \
  scribase admin setup --email you@yourdomain --setup-token scb_setup_...
$dc exec -e SCRIBASE_API_URL=http://127.0.0.1:8080 control-api \
  scribase org put <SCRIBASE_ORGANIZATION_ID value> "Your Org"

You can also open the console: on a control plane with no administrator, its sign-in page asks for the setup token instead of a password.

6. First project

Create a project and a production environment in the console, or with the CLI (see Organizations, projects and environments):

Terminal
scribase project put <org> my-app aws-eu-central-1
scribase env create <org> my-app production production snapshot aws-eu-central-1 first-env-1

The operator creates the environment's database, bootstraps the Supabase roles, publishes the runtime and creates the project container. Then check it with the host label and anon key the console shows on the environment's API keys page:

Terminal
host=https://<label>.<domain>
curl -fsS "$host/auth/v1/health"                          # starts the container
curl -fsS "$host/rest/v1/" -H "apikey: $ANON" -H "Authorization: Bearer $ANON"

Point an app at it with scribase apps switch and generate its types with scribase gen types.

Backups on the single box

Backups, verification restores and scheduled restore drills run inside the control API. Verification restores into the bundled restore-verify-db scratch server and never touches db or a project's own database. Check it after boot:

Terminal
$dc exec restore-verify-db pg_isready -U postgres -h localhost

A restore into an environment that already has data lands in a new restore_<id> database. Switching the environment to it (console: restore history, Switch) recreates the project container on the restored database. See Backups and restore.

Limits to know

  • The control API caps environments at SCRIBASE_MAX_ENVIRONMENTS (60 by default on this layout). On neon-cloud, your Neon plan's project limit also applies.
  • A cold request pays the container start (measured 0.7 s to healthy) plus the database start (well under a second for postgres-cow; typically 0.5 to 1 s for a Neon compute).
  • postgres-cow restores to a backup or to the head of the parent branch; it has no point-in-time restore to an arbitrary timestamp (use neon-cloud for that). Cutting a branch from a running environment pauses that environment's database for the length of the clone (milliseconds).
  • An engine move (scribase project move) pauses writes for the duration of the copy.

Copy-on-write storage

postgres-cow refuses to start on a filesystem that cannot clone files, because every branch would silently become a full copy. Most VPS images boot from ext4, so give the engine an XFS filesystem inside an image file:

Terminal
sudo apt-get install -y xfsprogs
sudo fallocate -l 30G /srv/scribase-pg-cow.xfs
sudo mkfs.xfs -m reflink=1 /srv/scribase-pg-cow.xfs
sudo install -d -m 700 /srv/scribase/pg-cow
echo '/srv/scribase-pg-cow.xfs /srv/scribase/pg-cow xfs loop,noatime,nofail 0 0' | sudo tee -a /etc/fstab
sudo mount /srv/scribase/pg-cow
sudo chown 10001:10001 /srv/scribase/pg-cow && sudo chmod 700 /srv/scribase/pg-cow

Check it from the worker: $dc exec worker scribase-postgres-cow status.