Operations
Portable logical backups
Portable logical backups with SHA-256 evidence and verified restore.
scribase-backup create is the production-safe primitive for independent logical
database exports. It uses PostgreSQL custom format, validates the completed
archive through pg_restore --list, calculates SHA-256 evidence, writes a
versioned JSON manifest, and publishes both files atomically without overwriting
an earlier artifact.
export SCRIBASE_BACKUP_DATABASE_URL='postgresql://backup-role:...@db/app?sslmode=require'
export SCRIBASE_BACKUP_DIRECTORY=/var/lib/scribase/backups
scribase-backup create acme billing production nightly-2026-09-13
The output directory must be an absolute path backed by durable encrypted
storage. The database URL is passed through PGDATABASE, never a process
argument, and is redacted from debug and error output. Use a dedicated login
with CONNECT plus the minimum schema/table/sequence privileges needed by
pg_dump; it must not have BYPASSRLS, role creation, or database creation.
Archive and manifest files are created with mode 0600. Publication uses
same-filesystem no-clobber links, so two workers cannot overwrite the same
backup ID. PostgresBackupExecutor::verify_artifact rechecks the byte length,
SHA-256 digest, and pg_restore readability before an archive is eligible for
restore.
Optional bounds:
| Variable | Default |
|---|---|
SCRIBASE_BACKUP_TIMEOUT_SECONDS |
3600 (maximum 24 hours) |
SCRIBASE_BACKUP_MAXIMUM_BYTES |
107374182400 (maximum 10 TiB) |
SCRIBASE_PG_DUMP_BINARY |
pg_dump |
SCRIBASE_PG_RESTORE_BINARY |
pg_restore |
SCRIBASE_SHA256SUM_BINARY |
sha256sum |
This primitive proves archive creation and offline readability. Durable control-plane backup records remain a separate required layer; a successful archive alone is not restore evidence.
Uploading to versioned object storage
scribase-backup upload copies a published archive and manifest to an
S3-compatible bucket (AWS S3, Cloudflare R2, MinIO, Backblaze B2) under
content-addressed keys. It reconstructs the artifact from disk and recomputes
its SHA-256 first, so a corrupt local file never reaches the bucket, and no
database connection is required.
export SCRIBASE_BACKUP_DIRECTORY=/var/lib/scribase/backups
export SCRIBASE_OBJECT_STORE_BUCKET=scribase-backups
export SCRIBASE_OBJECT_STORE_REGION=auto
export SCRIBASE_OBJECT_STORE_PREFIX=scribase
export SCRIBASE_OBJECT_STORE_ENDPOINT='https://ACCOUNT.r2.cloudflarestorage.com' # optional
export AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... # AWS_SESSION_TOKEN optional
scribase-backup upload acme billing production nightly-2026-09-13
Each object key embeds the file's SHA-256, so identical content always maps to
the same immutable key. Every put-object passes the expected SHA-256 and the
store rejects any body that does not match, giving server-side integrity on
ingest. Object-store credentials are injected through the child environment and
never appear in process arguments; the endpoint must be an https URL. The
upload is idempotent — re-running re-puts the same bytes to the same keys and
accepts a matching local *.upload.json receipt.
| Variable | Default |
|---|---|
SCRIBASE_OBJECT_STORE_ENDPOINT |
AWS default endpoints |
SCRIBASE_UPLOAD_TIMEOUT_SECONDS |
1800 (maximum 6 hours) |
SCRIBASE_AWS_BINARY |
aws |
SCRIBASE_SHA256SUM_BINARY |
sha256sum |
A restore into an isolated staging database with post-restore verification (the
prepare-restore-target and restore executor below) remains the final required
layer.
Guarding a restore target
Before any archive can be restored, a newly provisioned empty staging database must receive an identity-bound one-time guard:
export SCRIBASE_RESTORE_DATABASE_URL='postgresql://restore-role:...@db/staging?sslmode=require'
export SCRIBASE_RESTORE_ALLOW_MUTATIONS=isolated-staging-only
scribase-backup prepare-restore-target acme billing restore-check NONCE
Preparation runs in a serializable transaction under an advisory lock. It
refuses any existing user relation, creates only the scribase_restore_guard
schema, and records the organization, project, environment, and hex-encoded
nonce. An identical retry succeeds; a different identity or nonce fails. The
database URL remains in PGDATABASE and raw PostgreSQL errors are not exposed.
This guard is the mandatory prerequisite for the forthcoming restore executor;
the current command does not run pg_restore itself.