Skip to content

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 ​

console
$ 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 app

Without --force, a non-empty volume is refused:

console
$ docker compose run --rm dockup restore --source volume:appdata
error: refusing to restore into a non-empty volume (appdata); pass --force to overwrite

That 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 ​

console
$ 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:

console
$ 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 --force

Without Dockup — the archives are ordinary tar files, so any machine can read them:

console
# 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/ . --recursive

Migrating a volume to a different name ​

console
# 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 --force

Retention 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 ​

Contentsyes
Modesyes, exactly (a 0400 file comes back 0400)
Ownershipyes, when Dockup runs as root (the default)
Symlinks and hardlinksyes, recreated as links
Modification timesyes
Extended attributes / ACLsno
Sockets, fifos, devicesnever archived (skipped with a warning)

When a restore fails ​

SymptomCause
refusing to restore into a non-empty volumepass --force if overwriting is intended
archive is encrypted but encryption.age.identity is not configuredmount the age identity
cannot list … : no such hostthe target is unreachable; other targets are still listed
source is already being backed upa run is in flight; wait or check status

Released under the Mozilla Public License 2.0.