Snapshot the Database
An app on SQLite keeps copies of its database, taken as it changes, so a lost disk or a bad deploy costs minutes of data, not all of it:
SNAPSHOT_URL=s3://my-app-backups/database-snapshots/v1- After the app writes, through
ctx.dbordbin a view, action, job or command, a consistent copy (VACUUM INTO) goes toSNAPSHOT_URLabout a second later. Writes close together share one copy, and at most one is taken at a time, with one more after it for writes made meanwhile. - A copy is named for when it was taken:
2026-09-29T12-00-00.000Z-<uuid>.sqlite. It is written once, with a checksum, and never replaced. - Closing the database first takes the copy its last writes are waiting for: when a command ends, and when the server stops (a deploy or restart sends it SIGTERM).
SNAPSHOT_URLtakes the same forms asSTORAGE_URL:file:./data/snapshotsfor a folder, ors3://bucket/prefixwith the same credentials. See Store Files.- Give snapshots a place of their own, not
STORAGE_URLitself:SNAPSHOT_KEEPremoves the oldest.sqlitefiles it finds there. SNAPSHOT_KEEP=48keeps only the newest 48. Unset, every copy stays, for the bucket's lifecycle rules to expire.- A copy that fails is retried, then logged; the write it followed has already succeeded.
webstir snapshot --workspace <path>takes one now, of the database as it is: it applies no migration, and refuses when there is no database.
Webstir's own tables that change on almost every request, sessions and the job queue, don't trigger a copy by themselves; the next copy includes them.
Restore
Stop the app, replace its database file (data/app.sqlite, or the path DATABASE_URL names) with the copy, and start it again.
Postgres
Snapshots are for SQLite. A Postgres database is its host's to back up.