Label-driven Docker volume backup daemon, powered by restic
Containers opt in via labels. At the scheduled time the daemon backs up their
named volumes through ephemeral restic workers, optionally stopping the
container for the duration, then prunes old snapshots. Backups land in a restic
repository: a local Docker volume, S3, or an rclone remote.
| Label | Default | Purpose |
|---|---|---|
volkeep.enable |
required | true to opt this container in |
volkeep.stop |
false |
Stop the container during backup |
volkeep.volumes |
all named mounts | Comma-separated whitelist |
volkeep.retention-days |
daemon default | Daily snapshots to keep |
name: app
services:
app:
image: app
volumes:
- data:/data:rw
labels:
volkeep.enable: true
volkeep.stop: true
volumes:
data:Bind mounts and anonymous volumes are skipped. Snapshots are tagged with the volume name.
| Env | Default | Description |
|---|---|---|
VOLKEEP_SCHEDULE |
required | Daily fire time HH:MM (daemon TZ) |
VOLKEEP_HOST |
required | Identifier for restic snapshots --host |
RESTIC_REPOSITORY |
required | Restic URI, or volume:<name> (local) |
RESTIC_PASSWORD |
required | Restic repo password |
AWS_* |
— | Forwarded to workers (S3 backends) |
RCLONE_* |
— | Forwarded to workers (rclone backends) |
VOLKEEP_RETENTION_DAYS |
5 |
Daily snapshots to keep |
VOLKEEP_CHECK |
true |
Verify repo integrity after each pass |
VOLKEEP_JITTER |
0 |
Random pre-fire delay (e.g. 30m) |
VOLKEEP_RESTIC_IMAGE |
restic/restic |
Worker image |
DOCKER_HOST |
local socket | Override to reach a proxied daemon |
RESTIC_REPOSITORY selects the repository:
- Local —
volume:<name>uses a Docker named volume as the repo, backed by a bind mount or any driver viadriver_opts. - Remote — an S3 or rclone backend URI.
For rclone remotes, point VOLKEEP_RESTIC_IMAGE at an image bundling the
rclone binary (e.g. tofran/restic-rclone) and configure it with
RCLONE_CONFIG_*.
RESTIC_PASSWORD is fixed at repo init. Rotating it later locks you out of
existing snapshots. Use restic key add instead.
By design, each host runs its own daemon and repository. To share a single S3
bucket, give each host a distinct prefix (s3:s3.host.com/bucket/<host>) and
set VOLKEEP_JITTER to spread concurrent fires.
Run a backup pass on demand:
docker kill -s SIGUSR1 volkeepvolkeep needs access to the Docker API. compose.dev.yml
wires it through a socket-proxy and shows the full stack. The snippets
below cover only volkeep's own config.
Local:
name: volkeep
services:
volkeep:
image: ghcr.io/deadnews/volkeep
container_name: volkeep
environment:
VOLKEEP_SCHEDULE: 03:00
VOLKEEP_HOST: ${HOSTNAME:-web-1}
RESTIC_REPOSITORY: volume:volkeep_backup
RESTIC_PASSWORD: ${RESTIC_PASSWORD}
volumes:
backup:Remote:
name: volkeep
services:
volkeep:
image: ghcr.io/deadnews/volkeep
container_name: volkeep
environment:
VOLKEEP_SCHEDULE: 03:00
VOLKEEP_JITTER: 30m
VOLKEEP_HOST: ${HOSTNAME:-web-1}
RESTIC_REPOSITORY: s3:s3.host.com/bucket/${HOSTNAME:-web-1}
RESTIC_PASSWORD: ${RESTIC_PASSWORD}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}Backups are stored in an ordinary restic repository. Drive it with any
restic command (restore, mount). See the restic docs.
Local:
alias RESTIC='docker run --rm \
-e RESTIC_PASSWORD \
-v volkeep_backup:/repo \
restic/restic -r /repo'
RESTIC snapshots --tag app_data
RESTIC restore latest --tag app_data --target /tmp/outRemote:
alias RESTIC='docker run --rm \
-e RESTIC_PASSWORD \
-e AWS_ACCESS_KEY_ID \
-e AWS_SECRET_ACCESS_KEY \
restic/restic -r s3:s3.host.com/bucket/web-1'
RESTIC snapshots --host web-1 --tag app_data
RESTIC restore latest --host web-1 --tag app_data --target /tmp/out