Pre-release. bakelite is unreleased and still under active testing — docs and behaviour may change without notice.

Continuous SQLite backups you can rewind to any moment.

Objects are encrypted before they leave the box, copied to as many destinations as you list, and checked by actually restoring them. It reads SQLite's WAL straight from disk and watches the file with the OS, so it backs up the moment your data changes.

# Start replicating — one daemon watches every database in the config.

bakelite daemon --config bakelite.toml

starting replication db=app path=/srv/app.db

bootstrapping: taking initial snapshot db=app

wrote snapshot (new restore base) db=app index=0 pages=4 stored=4

ready; watching for changes db=app base=0 next_index=4 segments=0

# As your app writes, bakelite captures every commit and idles at ~0% CPU.

bakelite status

DATABASE STATE FRESH SYNCED CHANGES SHIPPED

app watching live 12s ago 3 5.19 KiB

# Every commit is a point you can roll back to:

bakelite list --db app

Restore points for "app" (1 backup(s)):

Restore points for this database: 2026-05-27T10:40:00Z → 2026-05-27T10:40:06Z

1 full backup(s) + 3 incremental change-set(s)

# Rewind to any moment — it writes a fresh file and never touches your live DB.

bakelite restore --db app --output /tmp/at.db --timestamp '5m ago'

Restored "app" -> /tmp/at.db

target: 5m ago -> 2026-05-27T10:40:04Z

base snapshot: index 0

applied: full backup + 2 incremental change-set(s)

✓ lineage verified: 2/2 change-set(s), hashes match

size: 4 pages x 4096 bytes (16.00 KiB)

downloaded: 4.41 KiB

✓ integrity_check: ok

Point-in-time

Rewind to any moment you've captured

Every commit is copied as it happens, so a restore isn't last night's snapshot — it's your database as it was at the moment you name, anywhere in the retained window. Undo a bad deploy, a botched migration, or an accidental DELETE, and come back to the exact state just before it. Restore writes a fresh file and never touches the live database.

How restore works →

Correctness

Restores checked against a recorded history

A backup is only useful if it actually restores, so the test suite restores them and checks the result. A torture harness drives a marked, self-checking workload through injected backend faults and hard kills, then checks that each restore is a gap-free, content-exact point in time.

See how it's proven →

Redundancy

One bad copy doesn't lose the backup

Copy every change to several places at once — local disk, a bucket, a second region. On restore bakelite validates every object as it reads it, and if an object at one destination fails that check, it reads the same object from another destination. bakelite repair then rewrites the bad object from a good copy, so a corrupt object at one provider doesn't cost you the backup.

Setup

Configured in a few lines

Point it at a database, say where the backups go, and start the daemon. Fanning out to several destinations and encrypting every object is a couple of lines more.

# Encrypt every object before it leaves the box.
[defaults.encryption]
key_file = "/etc/bakelite/key.txt"

# Back up one database to two places at once.
[[database]]
name = "app"
path = "/var/lib/app/app.db"
  [[database.backends]]
  type = "file"
  path = "/var/backups/bakelite"    # local mirror, instant restore
  [[database.backends]]
  type = "s3"
  bucket = "offsite-backups"
  prefix = "bakelite/app"           # cloud copy, off-site

Generate a key with bakelite keygen, then install · how it works