Backup and restore
Snapshot everything (or one bucket) to a portable .zip, restore it later, and —
when you're ready — put the whole thing on a schedule with retention and an
optional push off-box. Start with a one-command manual snapshot.
Back up now (the copy-paste version)
Download a whole-instance snapshot:
curl -sS http://localhost:9000/api/admin/backup \
-H "Authorization: Bearer $ADMIN_JWT" \
-o openbucket-snapshot.zip
Or just one bucket:
curl -sS http://localhost:9000/api/admin/buckets/acme-data/backup \
-H "Authorization: Bearer $ADMIN_JWT" \
-o acme-data.zip
That's a complete, self-describing archive — object bytes plus the metadata needed to rebuild the bucket(s).
In the console
Open Backup & restore. Click Download for a whole-instance snapshot, or
use a bucket's Backup action for a single-bucket .zip. Restore uploads the
archive back through the same page.
Restore (this RESETS the target)
Restore uploads a .zip and rebuilds from it:
# Rebuild the whole instance from a snapshot
curl -sS -X POST http://localhost:9000/api/admin/restore \
-H "Authorization: Bearer $ADMIN_JWT" \
--data-binary @openbucket-snapshot.zip
# Rebuild a single bucket
curl -sS -X POST http://localhost:9000/api/admin/buckets/acme-data/restore \
-H "Authorization: Bearer $ADMIN_JWT" \
--data-binary @acme-data.zip
Both return a small JSON summary — bucketsRestored / objectsRestored for the
instance route, objectsRestored for the bucket route.
Restoring the instance resets the instance; restoring a bucket resets that bucket to exactly the snapshot's contents. It is not additive — anything not in the archive is gone. Restore into a fresh instance, or be certain you mean to overwrite.
Uploads stream straight to disk (the global body parser is off on these routes),
so multi-hundred-megabyte archives don't buffer in memory. Restore is also
bounded by guard rails against a hostile archive: total bytes, per-entry bytes,
entry count, and manifest size are all capped (RESTORE_MAX_TOTAL_BYTES,
RESTORE_MAX_ENTRY_BYTES, RESTORE_MAX_ENTRIES, RESTORE_MAX_MANIFEST_BYTES).
Scheduled backups
Turn on a background runner that writes snapshots on a cron or fixed interval, prunes old ones by a retention policy, and optionally pushes each finished snapshot to your replication target.
Standalone (env)
OB_SCHEDULED_BACKUP_ENABLED=true
OB_SCHEDULED_BACKUP_SCOPE=instance # or `buckets` (one .zip per bucket)
OB_SCHEDULED_BACKUP_CRON="0 3 * * *" # 03:00 daily — XOR with INTERVAL_MINUTES
OB_SCHEDULED_BACKUP_KEEP_LAST=7 # keep the newest N (default 7)
OB_SCHEDULED_BACKUP_MAX_AGE_DAYS=30 # also keep anything younger (default 30)
OB_SCHEDULED_BACKUP_PUSH_TO_REPLICATION=false
# OB_SCHEDULED_BACKUP_DIR=/var/lib/openbucket/backups # default <dataDir>/backups
# OB_SCHEDULED_BACKUP_INTERVAL_MINUTES=1440 # instead of a cron
Embedded (forRoot)
OpenBucketModule.forRoot({
dataDir: '/var/lib/openbucket',
// ...rootCredentials, admin...
backups: {
scope: 'instance', // or 'buckets'
cron: '0 3 * * *', // XOR intervalMinutes
keepLast: 7,
maxAgeDays: 30,
pushToReplication: false,
// dir: '/var/lib/openbucket/backups', // default <dataDir>/backups
},
});
Exactly one of cron / intervalMinutes must be set; the cron expression is
validated at boot, so a typo fails fast instead of silently never firing.
Retention is a union
keepLast and maxAgeDays are combined with OR, not AND: a snapshot is
pruned only if it's both older than maxAgeDays and outside the newest
keepLast. In other words keepLast is a hard floor — you never drop below N
snapshots even if they're all ancient.
Each write is atomic (temp file, then rename) and guarded by a free-space check, and only one snapshot ever runs at a time.
Drive it from the API
# Redacted status — counts, timestamps, and policy (never the snapshot dir)
curl -sS http://localhost:9000/api/admin/backup/schedule \
-H "Authorization: Bearer $ADMIN_JWT"
# Trigger a snapshot now (202); a concurrent call joins the in-flight run
curl -sS -X POST http://localhost:9000/api/admin/backup/schedule/run-now \
-H "Authorization: Bearer $ADMIN_JWT"
run-now shares the same code path and in-flight lock as the scheduled tick, so
it can never launch a second overlapping snapshot — a concurrent request returns
{ "started": false }.
Push snapshots off-box
Set pushToReplication (env OB_SCHEDULED_BACKUP_PUSH_TO_REPLICATION=true) to
copy each finished snapshot to your configured replication
target under a reserved prefix — so a lost data
volume doesn't take your backups with it.
If you set the push flag but replication is off, the runner logs a boot-time
warning and writes snapshots locally only — the push is a no-op until you
configure OB_REPLICATION_*.
A .zip contains decrypted object bytes (SSE-S3 is a rest-encryption of the
local blobs, not the archive). Snapshots are written 0o600 inside a 0o700
directory, but the archive itself inherits your data volume's trust boundary —
store and transfer it accordingly.
Next steps
- Replication and tiering — the off-box target the push uses.
- Securing OpenBucket — protect secrets, snapshots, and the admin surface.
- Multi-tenancy — scoped keys and admin roles.