# Backups & restore

> Portable logical backups with SHA-256 evidence and verified restore.

Source: https://scribase.com/docs/backups

# Portable logical backups

`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.

```bash
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.

```bash
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-`put`s 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:

```bash
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.