Skip to content

You have backups.
But when did you last prove they restore?

Scheduled restores into a disposable sandbox, your own checks, and a signed evidence record of every proof — with the restore time measured.

probavi is a self-hosted, engine-agnostic platform that continuously proves database backups are restorable, and records each proof as signed, auditable evidence with measured restore times.

It takes no backups of its own. Your existing tools already do that well.

“Backup completed successfully” proves almost nothing

Section titled ““Backup completed successfully” proves almost nothing”

Backups fail quietly. Corruption in transit. A missing WAL or binlog segment. A version incompatibility that only shows itself during a restore. An encryption key nobody kept. Months of dutifully backing up the wrong database.

Every one of these passes the backup job and fails the restore, and the failure surfaces at the one moment you cannot afford it. Meanwhile “we have backups” is accepted as an answer. The question it answers is not the one that matters: when did you last restore one?

A drill is the unit of work — one scheduled restore-verification of one database.

  1. 1ProvisionA disposable, isolated runtime: a container, a Kubernetes Job, or a bare host over SSH.
  2. 2RestoreAn adapter performs a real restore of a real backup, driving the engine’s own tooling.
  3. 3CheckValidations run against the restored database, from “did it come up?” to your own SQL.
  4. 4RecordOne signed record is appended to the evidence log, and the sandbox is destroyed.
The two durations are recorded separately, so the number you measure against your recovery time objective is not inflated by the checks. Tracked across drills, it degrades long before it breaches the target.

The core knows nothing about pg_dump, WAL segments or binlogs. Every engine-specific step lives in an adapter — an ordinary external process speaking a documented protocol, which anyone can implement in any language.

Teardown is forced, including on failure paths. A sandbox holds production data, so nothing about its destruction is left to the happy path.

Nothing to install alongside it, nothing to keep alive between drills, and no agent on a database host.

  1. cron or a systemd timerWhatever already runs on your machines. Probavi ships no scheduler and no daemon of its own.
  2. probavi runOne drill, under a lock file and a timeout. Teardown is forced, including on failure paths.
  3. What it leaves behind
    • One signed evidence record
    • A Prometheus textfile
    • A signed webhook, if you want one

Exit code

0
backup proven restorable
1
recoverability failure (backup, restore, or check)
2
infrastructure error or cancelled
3
usage or setup error
5
evidence record could not be written
Probavi is the middle box. What starts it and what reads the result are things you already run — which is also why there is nothing here to keep alive between drills, and nothing to install on the database hosts themselves.

This is what makes continuously more than an adverb. A restore you run when you remember to is an exercise; one that runs whether or not anyone remembers is a record — and a history of recoverability is worth exactly as many drills as happened without being asked for.

The exit code is the contract, and it separates the two failures that matter. A backup that did not restore is not the same event as a container runtime that was down, and an alerting rule that cannot tell them apart gets muted inside a month. Both are recorded either way: an error is a drill outcome, not a missing line in the log.

A whole game-day runs the same way. probavi gameday takes member drills in dependency order and leaves each one its own signed record — the annual afternoon with a runbook, expressed as one more line in a timer.

Running a restore test is a copyable feature. A continuously maintained, cryptographically verifiable history of recoverability is not.

{
"adapter": {
"digest": "sha256:4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c",
"name": "postgres",
"protocol": "probavi-adapter/0",
"version": "0.1.0"
},
"backup": {
"checksum": "sha256:9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f",
"created_at": "2026-07-30T01:58:02.000Z",
"kind": "pgdump",
"size_bytes": 565248
},
"checks": [
{
"detail": "accepting connections",
"name": "service_healthy",
"ok": true
},
{
"detail": "100000 rows (min 100000)",
"name": "row_count:orders",
"ok": true
}
],
"drill": {
"config_hash": "sha256:7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d",
"name": "prod-orders-db",
"pitr_target": "2026-07-30T14:32:00.000Z"
},
"env": {
"arch": "amd64",
"host_id": "3f7a9c2e5b1d8e04",
"os": "linux",
"probavi_digest": "sha256:1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d",
"probavi_version": "0.1.0"
},
"error": null,
"outcome": "pass",
"prev_hash": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
"sandbox": {
"params": {
"image": "postgres:16",
"memory": "2GiB"
},
"provider": "docker"
},
"schema": "probavi-evidence/2",
"seq": 1,
"sig": {
"alg": "ed25519",
"key_id": "56475aa75463474c",
"sig_b64": "/f2ahJgj08qDVSvbTIZ/+XI7IfkCavp0WfS7ETIezC+E2PMzFzEmX/G8d4d+S42Q9ZegQDU8aMKJTBb/gS5KDQ=="
},
"timings_ms": {
"engine_ready": 1166,
"provision": 1170,
"restore": 190,
"total": 2840,
"transfer": 110,
"validate": 61
},
"ts": "2026-07-31T02:00:11.482Z"
}

A real record from the format’s published example log, re-indented for reading — on disk it is one canonical line, and that is the form the signature covers.

Signed with:03a107bff3ce10be1d70dd18e74bc09967e4d6309ba50d5f1ddc8664125531b8

Records are chained: each carries the SHA-256 of the one before it, and each is signed with ed25519. The log is append-only — no code path in Probavi rewrites or deletes a record that already exists.

record 1proven restorablegenesis — no predecessor
record 2recoverability failureprev_hash 30c0e1b670…
record 3drill errorprev_hash 1fab7db153…
  • each record carries the previous record’s hash
  • alter one record and every link after it stops verifying
The format’s published example log, in full — a pass, a failure, and an error. A log that recorded only successes would not be evidence of anything.

Checking all of it takes the log file and a public key. No network, no licence, and not even a Probavi installation, since the format ships with an independently released verifier. Verification is never paywalled. For a product whose output is proof, that is not a pricing decision.

Two pluggable axes either side of a core that knows nothing about databases or container runtimes.

Engine adapters
Apache Cassandracassandra
ClickHouseclickhouse
CouchDBcouchdb
DuckDBduckdb
Elasticsearchelasticsearch
etcdetcd
Firebirdfirebird
H2h2
InfluxDBinfluxdb
MariaDBmariadb
MongoDBmongodb
SQL Servermssql
MySQLmysql
Neo4jneo4j
OpenSearchopensearch
Oracle Databaseoracle
PostgreSQLpostgres
Prometheusprometheus
Qdrantqdrant
Redisredis
Apache Solrsolr
SQLitesqlite
Valkeyvalkey
VictoriaMetricsvictoriametrics
CoreOrchestrates, measures, signs. Knows no engine and no runtime.
Evidence log
Sandbox providers
Dockerdocker
Kubernetes Jobk8s
Bare host over SSHremotehost
Adding an engine or a runtime never touches the core — which is also why adapters can be written by anyone, in any language. Both columns are drawn from the capabilities manifest, so this diagram lists what ships rather than what was planned.
  • 24engine adapters
  • 3sandbox providers
  • 5check kinds
  • 8CLI commands
  • 24CLI languages
  • 4versioned specifications

Engines. Verified against means what it says: the versions this project’s integration suite restores from on every run. It is not a supported-version range, and the project makes no such claim.

EngineVerified againstSourcesPoint-in-time recovery
Apache Cassandra4.1, 5.0cassandra_snapshot cassandra_snapshot_dir cassandra_snapshot_tar
ClickHouse26.3, 26.7clickhouse_backup clickhouse_backup_dir
Apache CouchDB3.5.2, 3.4.3couchbackup couchbackup_dir couchdb_data couchdb_data_tar
DuckDB1.4, 1.5duckdb_db duckdb_db_dir duckdb_export
Elasticsearch8.19.20, 9.5.2elasticsearch_repo elasticsearch_repo_zip
etcd3.5, 3.6, 3.7etcd_snapshot etcd_snapshot_dir
Firebird5.0.4, 4.0.7firebird_gbak firebird_gbak_dir
H22.4.240, 2.3.232h2_backup h2_backup_dir h2_db h2_db_dir
InfluxDB2.7, 2.8, 2.9influx_backup influx_backup_dir influx_backup_tar
MariaDB10.11, 11.4, 11.8, 12.3mariadb_backup mariadb_dump mariadb_dump_dir
MongoDB7.0, 8.0mongodump mongodump_dir mongodump_with_oplog mongodump_with_users
Microsoft SQL Server2019, 2022, 2025bak bak_chain bak_dir bak_with_logins
MySQL8.4, 9.7Variant image: percona/percona-server:8.4.10mysqldump mysqldump_dir mysqldump_with_users xtrabackup
Neo4j5.26neo4j_dump neo4j_dump_dir
OpenSearch2.19.6, 3.8.0opensearch_repo opensearch_repo_tar
Oracle Database23.26.3.0oracle_datapump
PostgreSQL14, 15, 16, 17, 18Variant image: pgvector/pgvector:0.8.6-pg17Variant image: timescale/timescaledb:2.29.1-pg17Variant image: postgis/postgis:17-3.5pgbackrest pgdump pgdump_dir pgdump_with_globals timescaledb_dump timescaledb_dump_dirYes
Prometheus3.13prometheus_snapshot prometheus_snapshot_dir prometheus_snapshot_tar
Qdrant1.19.0, 1.18.1qdrant_full_snapshot qdrant_full_snapshot_dir qdrant_snapshot qdrant_snapshot_dir
Redis7.2, 7.4, 8.2, 8.10redis_aof redis_rdb redis_rdb_dir
Apache Solr10solr_backup solr_backup_dir solr_backup_tar
SQLite3.46, 3.49, 3.50, 3.51, 3.53sqlite_db sqlite_db_dir sqlite_dump sqlite_dump_dir
Valkey7.2, 8.0, 8.1, 9.0, 9.1valkey_aof valkey_rdb valkey_rdb_dir
VictoriaMetrics1.150victoriametrics_backup victoriametrics_backup_dir victoriametrics_backup_tar

Sandboxes. Where the restore happens, and what is guaranteed about it.

  • docker — Docker no published portsforced teardown
  • k8s — Kubernetes Job no published portsforced teardown
  • remotehost — Bare host over SSH no published portsforced teardown

Checks. What a drill can assert about the restored database.

  • service_healthy — Engine answers the adapter's healthcheck
  • table_exists — Table exists and is queryable
  • row_count — Row count within bounds
  • freshness — Newest row younger than a maximum age
  • sql — Custom SQL assertion (your own statement)

Commands. The whole surface of the binary.

  • probavi run
  • probavi gameday
  • probavi push
  • probavi evidence verify
  • probavi evidence keygen
  • probavi adapter probe
  • probavi adapter conformance
  • probavi version

Specifications. Versioned independently of the binary, so an integration can target a contract rather than a release.

The boundaries are part of the offer, so they are stated rather than implied.

  • Probavi takes no backups. It verifies backups produced by other tools — pgBackRest, wal-g, Barman, mysqldump and their equivalents — which are its foundation, never its competitors.
  • Probavi ships no scheduler and no daemon of its own. Drills are started by cron or a systemd timer, with a lock file and a timeout.
  • Probavi runs no agent or daemon on database hosts.
  • Probavi manages no secrets. It reads credentials from environment variables or files for the duration of one drill, and redacts them from logs and evidence.
  • Probavi has no telemetry and never phones home. It is a trust product; that is not negotiable.
  • Probavi ships no web interface. The shipped surfaces are the command line, a Prometheus textfile, and webhooks.

It stands on your backup tools, not against them

Section titled “It stands on your backup tools, not against them”
Your backup tools
pg_dumppgBackRestmysqldumpxtrabackupmariadb-backupmongodumpcouchbackupBACKUP DATABASEexpdpgbakBACKUP … TO Filenodetool snapshotneo4j-admin database dumpetcdctl snapshotredis-cli --rdbvalkey-cli --rdbsqlite3 .backupEXPORT DATABASEBACKUP TO_snapshotaction=BACKUP/snapshotstsdb/snapshotinflux backupvmbackup
Already good at what they do. Probavi replaces none of them.
Probavi drillRestore into a sandbox, check, measure, tear down.
Signed evidenceVerifiable offline by anyone holding the public key.
The arrow into Probavi comes from your existing tools. Treating them as the foundation rather than the competition is a permanent choice, not a stage the project intends to grow out of.

That is also why this is worth self-hosting. Managed restore testing exists inside single cloud ecosystems, and enterprise backup suites verify at the virtual-machine level. Neither helps a mixed estate of databases on your own machines.

DORA, NIS2 and NIST’s contingency-planning guidance all expect recovery capability to be tested and documented, not merely configured. Automated drills produce exactly that: dated, signed records of what was restored, how long it took, and what was verified about the result.

The complete verification loop is open source under Apache-2.0 and stays that way: drills and game-days, adapters, sandbox providers, checks, evidence generation, evidence verification, the command line, the metrics. Anyone can prove their backups restore, indefinitely, without asking permission. The specifications and the independent verifier are public for the same reason — evidence that only its producer can check is not evidence.

Nothing in the binary is unlocked by a network call. Any licence key it ever checks is an ed25519-signed token, verified offline — a product that promises never to phone home cannot make an exception for its own billing.

A drill is a file:

drill.yaml
target:
name: prod-orders-db
adapter: postgres
source:
kind: pgdump
path: /backups/orders/latest.dump
sandbox:
provider: docker
params:
image: postgres:16
timeout: 30m
checks:
- builtin: service_healthy
- builtin: row_count
table: orders
min: 100000
evidence:
path: /var/lib/probavi/evidence.jsonl
sign_key: /etc/probavi/ed25519.key

Then one command per drill, and one to check the log:

Terminal window
$ probavi run --config drill.yaml
{"outcome":"pass","seq":42,"evidence_path":"/var/lib/probavi/evidence.jsonl","checks_passed":3,"checks_total":3,"restore_ms":252400,"total_ms":318900}
$ probavi evidence verify --log /var/lib/probavi/evidence.jsonl --key probavi.key.pub
{"status":"VALID","records":42,"damaged_lines":[],"failed_line":0,"reason":"","head":{"seq":42,"hash":"sha256:1a8f736d2f4350385693385d203799f88721db4f4fd5056d6ceb028088127268"}}

The repository has the quickstart, the specifications, and the manifest of everything above.