Volumes: snapshots, backups and staged restores
Data that matters lives on named volumes: an app's data, a database's files, a workspace's home. isb protects them three ways: cheap snapshots on the volume's own pool, now or on a schedule; backups to the org's S3 destinations; and restores that land beside the live volume, never over it, so you compare and copy back only what you need.
An org's named volumes are the custom incus volumes in its project: an
app's volumes (<project>-<env>_<app>_<NAME>, Deploy apps),
a database's data (Databases), a workspace's home
(Workspaces).
$ isb --org acme volume snapshot schedule web_data --schedule '0 * * * *' --keep 24
web_data: next snapshot 2026-10-03T21:00:00Z
$ isb --org acme volume snapshot create web_data --as before-upgrade
snapshot of web_data: run 3 started
isb: running /etc/isb/pre-snapshot in web-1 (timeout 300s)
isb: snapshotting web_data as before-upgrade
run 3: succeeded
$ isb --org acme backup create web-data-nightly --volume web_data --destination offsite --schedule '0 3 * * *' --keep 14
$ isb --org acme volume restore web_data --snapshot before-upgrade
restoring web_data into web_data-restore-20261003T201304Z (restore run 4); it will be at /restore/20261003T201304Z
isb: copying snapshot web_data/before-upgrade to web_data-restore-20261003T201304Z
isb: mounted at /restore/20261003T201304Z in web-1, read-write; the live volume is untouched
run 4: succeeded
$ isb --org acme volume discard web_data 20261003T201304Z
In the web UI, the Volumes page (linked from Backups) lists the org's volumes, and a volume's page (the Volume panel, also on a workspace's Home tab) shows its snapshots, schedule and hook, backups and staged restores (The web UI).
For an org placed on a server, all of this runs on the server's agent, through the control plane as usual; the org's destinations must be reachable from that server.
Snapshots#
incus snapshots of the volume, on its own pool (cheap, and lost with the
pool: back up for that). On a dir pool every snapshot is a full copy of
the volume, as large and as slow as the volume itself; see
Workspaces.
- Scheduled (
volume_snapshot_schedule): a cron schedule (Scheduled jobs),timezone,keep(default 7; 1 to 1000),missed_grace. They are namedauto-<YYYYMMDDTHHMMSSZ>, and after each one the oldestauto-snapshots beyondkeepare deleted. - Now (
volume_snapshot_create): named as given (before-upgrade) ormanual-<stamp>. Never pruned: kept until deleted (volume_snapshot_delete). - Every snapshot is a run with a log (
volume_snapshot_runs,volume_snapshot_run_log): the hook's output, the snapshot's name, what was pruned. A run due while the previous is still going isskipped.
The pre-snapshot hook#
Before every snapshot, and before every backup's snapshot, each running
instance using the volume runs its /etc/isb/pre-snapshot if it has one: as
root, with ISB_VOLUME, ISB_REASON (snapshot or backup) and
ISB_SNAPSHOT in its environment. It is how an image makes its own state
consistent first (a SQLite checkpoint, a flushed session file); isb knows
nothing about what the instance runs. Stopped instances need no hook.
- Its output goes to the run's log.
hook_timeout(default5m, at most1h): past it the hook is killed (SIGKILL to the hook's process; anything it started itself is not).- A hook that fails (exits non-zero, is not executable, times out) is
reported in the log, and by default the snapshot is taken anyway.
hook_required: truemakes a failure stop the snapshot (and the backup): the run fails and says why.
Backups#
A volume backup is a backup whose source is a
volume instead of a database: same destinations, schedules, keep,
compression, runs, logs and backup.succeeded/backup.failed events, listed
on the Backups page beside the database backups. Each run:
- runs the hook and takes a snapshot
isb-backup-<stamp>, - copies it to a temporary volume
<volume>-backup-<stamp>(nothing writes to it: the export is exactly the snapshot), - has incus export that volume (an uncompressed incus backup tarball,
written by incus to its own backups directory on the host while it is
read; incus expires a forgotten one after a day), compresses the stream
and uploads it to
<prefix>/<org>/<backup>/<volume>-<YYYYMMDDTHHMMSSZ>.volume.tar[.gz|.zst], - deletes the export, the temporary volume and the snapshot (also on
failure), checks the object with
HEAD, and deletes the oldest of the backup's objects beyondkeep(only objects named as above).
backup_restore refuses a volume backup: volume backups restore staged,
below.
Staged restores#
volume_restore (isb volume restore) restores a snapshot, a volume
backup (its newest file, or key), or a destination and key (a backup
deleted since), into a new volume <volume>-restore-<stamp>:
- mounted read-write at
/restore/<stamp>in the instance using the volume (a running one first, orinstance), as a disk deviceisb-restore-<stamp>; - left detached when that instance is stopped (or nothing uses the volume);
- never written over the live volume. Compare, copy back what you need
(
diff -r /restore/<stamp> ~), then discard it.
A snapshot restores as a copy of the snapshot; a backup streams from the
bucket through the daemon into an incus import. File ownership is kept.
Restores are the org's restore runs (isb backup runs --restores, the
Backups page's Restores), and emit volume.restore.staged or
volume.restore.failed.
The staged volume says what it is in its config: user.isb.restore-of,
-from (snapshot:<name> or backup:<key>), -stamp, -by and
-instance. volume_restore_list lists them; volume_restore_discard
(isb volume discard VOLUME STAMP) detaches it, removes the empty mount
point and deletes it, and refuses any volume without those keys.
Who may#
Members and viewers read: volumes, snapshots, runs, logs, staged restores. Taking and deleting snapshots, the schedule and hook, volume backups (create, update, run, delete) and restores are for the org's admins and owners (and platform admins, superadmins, the local socket). Every call is in the audit log with the volume, snapshot, backup and stamp it named.
isb takes the snapshots and exports, so an org's incus project allows them
(restricted.snapshots and restricted.backups); a project without them
gets both on its first snapshot.
History#
Events land in the history as
volume.snapshot.created, volume.snapshot.failed,
volume.snapshot.deleted, volume.restore.staged, volume.restore.failed
and volume.restore.discarded, about the volume (object type volume);
backups are backup.succeeded/backup.failed as for databases.
Tools#
| Tool | Does |
|---|---|
volume_list |
The org's volumes: instances using each, its snapshot schedule, staged restores. |
volume_get |
One volume: instances, settings and next run, snapshots, staged restores, backups of it. |
volume_snapshot_list |
Snapshots, newest first, with kind (auto, manual, other). |
volume_snapshot_create |
Snapshot now (snapshot name; wait). |
volume_snapshot_delete |
Delete a snapshot. |
volume_snapshot_schedule |
Schedule, keep, hook_timeout, hook_required (a merge patch; schedule: null removes it). |
volume_snapshot_runs, volume_snapshot_run_log |
Snapshot runs and their logs. |
volume_restore |
Restore staged from a snapshot, backup (+key) or destination + key; instance; wait. |
volume_restore_list, volume_restore_discard |
Staged restores; detach and delete one by stamp. |
backup_create with volume |
A volume backup (then backup_* as for databases). |
CLI#
isb volume show NAME snapshots, schedule, backups, staged restores (JSON)
isb volume snapshot create NAME [--as SNAP] [-d]
isb volume snapshot ls NAME [--json] | rm NAME SNAP | runs NAME | logs NAME [RUN]
isb volume snapshot schedule NAME [--schedule CRON | --off] [--timezone TZ] [--keep N]
[--hook-timeout 5m] [--hook-required | --hook-optional]
isb volume restore NAME (--snapshot S | --backup B [--key K] | --destination D --key K) [--instance I] [-d]
isb volume restores [NAME] | discard NAME STAMP
isb backup create NAME --volume V --destination D --schedule CRON [--keep N] [--compression C]
All take the global --org and go through isb serve. isb volume create|ls|inspect|rm talk to incus directly (CLI).
On disk#
<org root>/volumes/<volume>/volume.json settings and the scheduler's anchor
<org root>/volumes/<volume>/runs/<id>.json, <id>.log
Volume backups live with the other backups (<org root>/backups/schedules/),
staged restores' runs with the other restores (backups/restores/runs/).
Files are 0600.