Restoring and migrating
Dockup never restores anything without being asked, and never overwrites a volume that still has content unless you insist.
Restore a volume in place
$ docker compose run --rm dockup list --source volume:appdata
$ docker compose run --rm dockup restore --source volume:appdata --dry-run
$ docker compose stop app
$ docker compose run --rm dockup restore --source volume:appdata --force
$ docker compose start appWithout --force, a non-empty volume is refused:
$ docker compose run --rm dockup restore --source volume:appdata
error: refusing to restore into a non-empty volume (appdata); pass --force to overwriteThat refusal is the point: a typo in --source cannot silently replace a live volume.
Stop the workload first
A restore writes files into the volume while the application may be writing too. Dockup does not stop containers during a restore — you decide. For a database, docker compose stop db first.
Restore a specific archive
$ docker compose run --rm dockup restore --source volume:appdata \
--archive dockup/nas-01/volume/appdata/daily/appdata_20261015T032300Z.tar.zst.age--archive latest is the default; --target picks which destination to read from (the first target of the source otherwise).
Restore onto a fresh host
The state volume only holds history, metrics and locks. Archives are self-describing, so a new host needs nothing but the archives and a container.
With Dockup — create the volume, label it, restore:
$ docker volume create appdata
$ docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v dockup-state:/var/lib/dockup \
-v ./dockup.yml:/etc/dockup/dockup.yml:ro \
gitea.vladfaust.com/vladfaust/dockup:latest plan
$ docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v dockup-state:/var/lib/dockup \
-v ./dockup.yml:/etc/dockup/dockup.yml:ro \
gitea.vladfaust.com/vladfaust/dockup:latest restore --source volume:appdata --forceWithout Dockup — the archives are ordinary tar files, so any machine can read them:
# local target
$ zstd -dc appdata_20261102T032300Z.tar.zst \
| docker run --rm -i -v appdata:/data busybox tar xf - -C /data
# age-encrypted archive
$ age -d -i age-identity.txt appdata_20261102T032300Z.tar.zst.age \
| zstd -dc \
| docker run --rm -i -v appdata:/data busybox tar xf - -C /data
# S3
$ aws s3 cp s3://dockup-nas-01/dockup/nas-01/volume/appdata/daily/ . --recursiveMigrating a volume to a different name
# 1. archive the old volume (add dockup.enable=true to it first, or back it up
# from a container that mounts it)
$ docker compose run --rm dockup backup --source volume:old-name
# 2. create and label the new volume, then restore into it
$ docker volume create new-name
$ docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
-v dockup-state:/var/lib/dockup -v ./dockup.yml:/etc/dockup/dockup.yml:ro \
gitea.vladfaust.com/vladfaust/dockup:latest restore --source volume:new-name --archive latest --forceRetention is keyed by the source name, so the new volume starts with its own history. Keep the old archives until you are sure.
What is restored
| Contents | yes |
| Modes | yes, exactly (a 0400 file comes back 0400) |
| Ownership | yes, when Dockup runs as root (the default) |
| Symlinks and hardlinks | yes, recreated as links |
| Modification times | yes |
| Extended attributes / ACLs | no |
| Sockets, fifos, devices | never archived (skipped with a warning) |
When a restore fails
| Symptom | Cause |
|---|---|
refusing to restore into a non-empty volume | pass --force if overwriting is intended |
archive is encrypted but encryption.age.identity is not configured | mount the age identity |
cannot list … : no such host | the target is unreachable; other targets are still listed |
source is already being backed up | a run is in flight; wait or check status |