incus-compose backup takes the storage volumes of a project, keeps them, and puts them back. Here is five and a half minutes of it on the immich example — postgres, redis, server, microservices, machine-learning, and three volumes between them: asciinema.org/a/1263992.

Every timestamp below opens the recording at that moment, so you do not have to watch the whole thing.

It is one take. There is a ^C out of backup create while it was still waiting on a healthcheck, and a [warn: resource is already running] near the end.

Five commands

incus-compose -P examples/immich backup create
incus-compose -P examples/immich backup list
incus-compose -P examples/immich backup verify [<timestamp>]
incus-compose -P examples/immich backup restore
incus-compose -P examples/immich backup delete <timestamp>

list (1:51) is one line per backup. The one showing here is left over from an earlier run:

TIMESTAMP                      NAME  VOLUMES  POOL     SIZE
2026-08-26T22:47:11.56456765Z  ---   4        default  65.5kB

Four volumes in 65.5 kB — a 44 kB photo, the postgres database and not much else — because backups after the first one are incremental. Deleting that one (1:58) says what it costs:

WRN Deleting the newest backup; the next one transfers everything again
Deleted the backup taken at 2026-08-26T22:47:11.56456765Z

create (3:22) then makes the full transfer the warning promised. It stops every instance before it touches a volume, backs up postgres, library and model-cache, and starts them again afterwards. A copy taken while postgres is writing is not a backup.

verify checks that a backup’s restore points are all there, without restoring anything. It is the one command not in the recording.

Backup volumes land in the client’s default storage pool, which is why list above says default. Most people will want them on a different one — --pool=nvme1 on any of the five, x-incus-compose.backup.pool in the compose file, or INCUS_COMPOSE_BACKUP_POOL in the environment.

Then delete everything

The part of the recording worth watching is not the backup. It is this:

incus-compose -P examples/immich down --volumes
incus-compose -P examples/immich up --no-start
incus-compose -P examples/immich backup restore
incus-compose -P examples/immich up -d

down --volumes (4:07) removes the instances and the storage volumes with them. up --no-start (4:13) builds the project back empty. restore (4:30) names what it is about to write — vol-model-cache, vol-library, vol-postgres — asks once, and puts them back. up -d (5:17) starts the project in forty seconds.

The same file

There is one uploaded photo in the library. With the backup taken, before anything gets destroyed (3:54):

/data/upload/223d6a00-4c98-4662-a42d-a56d43a25e9d/5b/0b:
total 44
-rw-r--r-- 1 root root 44143 Aug 26 18:40 5b0b560a-563e-4f5a-8859-3df88270215c.png

After down --volumes, with the project rebuilt by up --no-start and nothing started yet, there is nothing to ask (4:16):

Error: Instance is not running

And after backup restore and up -d (5:20):

/data/upload/223d6a00-4c98-4662-a42d-a56d43a25e9d/5b/0b:
total 44
-rw-r--r-- 1 root root 44143 Aug 26 18:40 5b0b560a-563e-4f5a-8859-3df88270215c.png

Same path, same size, same mtime. The thumbnails came back with it.

Alongside all of it in the second pane, incus-compose healthd logs -f follows the run as lifecycle events: every instance started, stopped and deleted, every health transition, and at the end Dropping project.


By René Jochum. License: CC-BY-4.0.