Multiarch fork of borgmatic-collective/docker-borgmatic running on S6 Overlay. Supports linux/amd64 and linux/arm64.
Includes Borg, Borgmatic, Apprise, and optional Docker CLI for container stop/start hooks.
| Tag | Description |
|---|---|
latest |
Latest Borgmatic + Borg |
2.x.x-1.x.x |
Specific Borgmatic–Borg version pair |
services:
borgmatic:
image: modem7/borgmatic-docker
container_name: borgmatic
hostname: borgmatic
volumes:
- /home:/mnt/source:ro
- ./data/repository:/mnt/borg-repository
- ./data/borgmatic.d:/etc/borgmatic.d/
- ./data/.config/borg:/root/.config/borg
- ./data/.ssh:/root/.ssh
- ./data/.cache/borg:/root/.cache/borg
- ./data/.state/borgmatic:/root/.local/state/borgmatic
environment:
- TZ=Europe/London
- BORG_PASSPHRASE=changeme
restart: unless-stopped
stop_grace_period: 10mCopy .env.template to .env, fill in your values, then:
docker compose up -dInitialise your Borg repository:
docker exec -it borgmatic borgmatic repo-create --encryption repokey-blake2Run a manual backup to verify everything works:
docker exec -it borgmatic borgmatic --stats -v 1 --filesGenerate a full example config:
docker exec borgmatic borgmatic config generate -d /etc/borgmatic.d/config.yamlArchive naming gotcha: By default borgmatic uses
{hostname}in archive names. Docker container hostnames change on every rebuild, which breaks pruning and compaction. Set an explicit archive name in your config:archive_name_format: 'myhost-{now:%Y-%m-%d-%H%M%S}'
The image ships example configs in /etc/borgmatic.d/ that you can use as a starting point. When you mount your own config directory, place your config there:
| File | Purpose |
|---|---|
config.yaml |
Minimal working config for a local repository |
config.yaml.example |
Remote repository with SSH and passcommand |
config.full.yaml.example |
Annotated reference covering all common options |
before-backup.example |
Hook script template run before each backup |
after-backup.example |
Hook script template run after each successful backup |
failed-backup.example |
Hook script template run on error |
To use the hook scripts, make them executable and reference them in your config:
commands:
- before: everything
run:
- /etc/borgmatic.d/before-backup
- after: everything
states: [finish]
run:
- /etc/borgmatic.d/after-backup
- after: everything
states: [fail]
run:
- /etc/borgmatic.d/failed-backupBorgmatic expands ${VAR} references inside config files at runtime, which is useful for keeping secrets and paths out of your committed config:
source_directories:
- ${BORG_SOURCE_1}
- ${BORG_SOURCE_2}
repositories:
- path: ${BORG_REPO}
label: remotePass them via your compose file:
environment:
- BORG_SOURCE_1=/mnt/data
- BORG_SOURCE_2=/mnt/media
- BORG_REPO=user@borg.example.com:myrepoBorgmatic has built-in Healthchecks.io integration. Add your ping URL to your config:
healthchecks:
ping_url: ${BORG_HEALTHCHECK_URL}environment:
- BORG_HEALTHCHECK_URL=https://hc-ping.com/your-uuid-hereBorgmatic will ping on start, success, and failure automatically.
| Variable | Description | Default |
|---|---|---|
TZ |
Container timezone | Europe/London |
BORG_PASSPHRASE |
Repository encryption passphrase | — |
BORG_PASSPHRASE_FILE |
Path to a file containing the passphrase (see Secret files) | — |
BORG_RSH |
SSH command for remote repos, e.g. ssh -i /root/.ssh/id_ed25519 -p 50221 |
— |
CRON |
Cron schedule for borgmatic (see Scheduling) | — |
CRON_COMMAND |
Command cron runs | borgmatic-start --stats -v 0 2>&1 |
EXTRA_CRON |
Additional cron lines appended verbatim | — |
DOCKERCLI |
Set to true to install Docker CLI and Compose at startup |
— |
EXTRA_PKGS |
Space-separated Alpine packages to install at startup | — |
DEBUG_SECRETS |
Set to true or 1 to log secret variable values before/after expansion |
— |
Any Borg or borgmatic environment variable is passed through automatically — see the borgmatic and Borg documentation for the full list.
Three modes, checked in this order:
1. CRON environment variable
Standard 5-field cron expression:
environment:
- CRON=0 2 * * *2. crontab.txt file
Mount a file at /etc/borgmatic.d/crontab.txt. Use borgmatic-start (not borgmatic) to keep signal handling working:
0 2 * * * borgmatic-start --stats -v 0 2>&1
3. Built-in default
If neither CRON nor crontab.txt is present, borgmatic runs daily at 01:00.
Extra jobs with EXTRA_CRON:
environment:
- EXTRA_CRON=0 6 * * 0 borgmatic-start --stats -v 0 2>&1Disable cron entirely:
environment:
- CRON=falseThe container uses borgmatic-start as the default cron command. When docker stop sends SIGTERM, borgmatic-start forwards it to borgmatic so an in-progress backup can exit cleanly and release its repository lock rather than being killed mid-run.
Set stop_grace_period to give borgmatic enough time to finish:
services:
borgmatic:
stop_grace_period: 10m # adjust to suit your backup size and speedThe container will print a warning at startup if it detects borgmatic being called directly (instead of borgmatic-start) in the active crontab.
Any BORG_* or YOUR_* environment variable ending in _FILE is read from the named file and exported as the base variable at startup. This is the recommended approach for Docker Swarm secrets or any secret management system that writes files:
environment:
- BORG_PASSPHRASE_FILE=/run/secrets/borg_passphrase
secrets:
- borg_passphraseIf both BORG_PASSPHRASE and BORG_PASSPHRASE_FILE are set, the file value takes precedence.
When running borgmatic manually via docker exec, secrets are already available in the S6 container environment:
docker exec borgmatic sh -c \
'export BORG_PASSPHRASE=$(cat /run/s6/container_environment/BORG_PASSPHRASE) \
&& borgmatic list'Mount a directory to /custom-cont-init.d/ containing .sh scripts. They run after packages are installed but before cron starts, in filename order. Useful for generating SSH keys, importing GPG keys, or any one-time setup:
volumes:
- ./my-init-scripts:/custom-cont-init.d:roScripts run as root. A non-zero exit code logs a warning but does not abort startup.
| Path | Description |
|---|---|
/mnt/source |
Data to back up — mount read-only for safety |
/mnt/borg-repository |
Local Borg repository |
/etc/borgmatic.d/ |
Borgmatic config files and optional crontab.txt |
/root/.config/borg |
Borg config and keyfiles — back these up |
/root/.ssh |
SSH keys for remote repositories |
/root/.cache/borg |
Borg chunk cache (speeds up deduplication) |
/root/.local/state/borgmatic |
Borgmatic state for periodic check tracking |
/custom-cont-init.d/ |
Custom init scripts (optional) |
Apprise is included and integrates with Telegram, Slack, Discord, email, and many more services.
Add notification hooks to your borgmatic config:
commands:
- after: everything
states: [fail]
run:
- apprise -vv -t "Backup FAILED" -b "Borgmatic error on $(hostname)" \
"mailtos://smtp.example.com:587?user=you@example.com&pass=secret&to=you@example.com"Or use borgmatic's native Apprise config (borgmatic 1.8.4+):
apprise:
services:
- url: slack://token@Txxxx/Bxxxx/Cxxxx
label: slack
- url: mailtos://smtp.example.com:587?user=you@example.com&pass=secret&to=you@example.com
label: email
states:
- start
- finish
- fail
finish:
title: Backup succeeded
fail:
title: Backup failedBorgmatic's logs are automatically appended to notification bodies from borgmatic 1.8.9+.
Set DOCKERCLI=true to install Docker CLI and Compose at startup, then mount the Docker socket to allow borgmatic hooks to stop and start other containers:
environment:
- DOCKERCLI=true
volumes:
- /var/run/docker.sock:/var/run/docker.sockRather than hardcoding container names, label the services you want paused during backup and let the scripts discover them automatically:
# in each service that should pause during backup:
services:
myapp:
labels:
- backupThe image ships ready-to-use docker-stop.sh and docker-start.sh scripts in data/borgscripts/ — see Example borgscripts for setup details. For quick inline use:
commands:
- before: everything
run:
- docker ps -q -f "label=backup" | xargs --no-run-if-empty docker container stop -t 60
- after: everything
states: [finish, fail]
run:
- docker compose --project-directory /opt/docker/mystack startUsing states: [finish, fail] on the start command ensures containers are never left stopped, even if the backup fails.
Without Docker CLI, you can still control containers via the socket API directly:
commands:
- before: everything
run:
- 'echo -ne "POST /v1.41/containers/mycontainer/stop HTTP/1.1\r\nHost: localhost\r\n\r\n" | nc local:/var/run/docker.sock 80 > /dev/null'
- after: everything
states: [finish, fail]
run:
- 'echo -ne "POST /v1.41/containers/mycontainer/start HTTP/1.1\r\nHost: localhost\r\n\r\n" | nc local:/var/run/docker.sock 80 > /dev/null'Use
after: everythingwithstates: [finish, fail]to restart containers whether the backup succeeded or failed.
If a backup target is not always available (removable drive, remote server), use a before: action command with exit code 75 to skip gracefully without triggering error notifications:
commands:
- before: action
when: [create]
run:
- findmnt /mnt/backup-drive > /dev/null || exit 75See the borgmatic soft failure docs and backup to a removable drive for more.
Mounting a Borg archive requires extra privileges. Use the provided restore compose override:
docker compose down
docker compose -f docker-compose.yml -f docker-compose.restore.yml run borgmaticThe restore compose override sets command: /bin/sh, so the container drops you straight into a shell. If you need to attach to an already-running container instead:
docker exec -it borgmatic /bin/bashOnce inside, mount the archive, copy your files, then clean up:
# list available archives
borg list /mnt/borg-repository
# mount a specific archive (or the whole repo to browse all archives)
mkdir -p /mnt/restore
borg mount /mnt/borg-repository::archive-name /mnt/restore
# copy files back to their original location or inspect them
cp -a /mnt/restore/path/to/file /original/path/
# unmount and exit when done
borg umount /mnt/restore
exitThe required capabilities (SYS_ADMIN, /dev/fuse, AppArmor/SELinux options) are pre-configured in docker-compose.restore.yml.
If Borg has a stale lock from a previously interrupted backup:
borg break-lock /mnt/borg-repositoryPass borgmatic and its subcommand as the container command. S6 starts, runs
all init scripts, executes borgmatic, then shuts down. The
crond crashed with signal 15 message at the end is expected and harmless —
it is S6 cleanly stopping crond when borgmatic exits.
docker run --rm -it \
-v /home:/mnt/source:ro \
-v ./data/repository:/mnt/borg-repository \
-v ./data/borgmatic.d:/etc/borgmatic.d/ \
-v ./data/.config/borg:/root/.config/borg \
-e BORG_PASSPHRASE=changeme \
modem7/borgmatic-docker \
borgmatic listAll output is timestamped by the S6 logging pipeline and sent to Docker's log driver:
docker logs borgmatic
docker logs -f borgmatic # followBorgmatic can be verbose. Add a log rotation policy to prevent Docker logs from growing unboundedly:
services:
borgmatic:
logging:
driver: local
options:
max-size: 10m
max-file: "3"Enable debug logging for secret variable expansion:
environment:
- DEBUG_SECRETS=trueHook scripts can live anywhere accessible to the container. A dedicated volume keeps them separate from borgmatic config:
volumes:
- ./data/borgscripts:/borgscripts:rocommands:
- before: everything
run:
- /borgscripts/docker-stop.sh
- after: everything
states: [finish, fail]
run:
- /borgscripts/docker-start.shMake sure the scripts are executable (chmod +x). The container ships ready-to-use example scripts in data/borgscripts/ — copy them into your own borgscripts/ directory and edit the configuration variables at the top of each file.
The container also ships minimal hook templates in /etc/borgmatic.d/ (before-backup.example, after-backup.example, failed-backup.example).
The following scripts are provided in data/borgscripts/. They all require DOCKERCLI=true and the Docker socket mounted:
environment:
- DOCKERCLI=true
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./data/borgscripts:/borgscripts:roStops any container carrying a backup label before borgmatic runs. Using a label rather than hardcoded names means you only need to update your other compose files, not this script, when services change.
Label the services you want paused in their own compose files:
services:
myapp:
labels:
- backup
mydb:
labels:
- backupThe script has two configuration variables at the top:
BACKUP_LABEL="backup" # label key to filter containers — change if you use a different label
STOP_TIMEOUT=60 # seconds to wait for graceful shutdown before force-killStarts a compose stack after borgmatic finishes. Uses docker compose start rather than up -d to restart only containers that were previously running — it will not recreate services, pull new images, or start anything that was already stopped before the backup began.
Edit the configuration variable at the top:
COMPOSE_DIR="/opt/docker/mystack" # absolute path to your compose project directoryWire both scripts together in your borgmatic config. Using states: [finish, fail] on the start command ensures containers are never left stopped, even when a backup errors:
commands:
- before: everything
run:
- /borgscripts/docker-stop.sh
- after: everything
states: [finish, fail]
run:
- /borgscripts/docker-start.shRedis supports live backups via BGSAVE — the container does not need to stop. This script triggers a background save and waits for it to complete. When borgmatic runs immediately after, it backs up the resulting dump.rdb as a regular file.
Configuration variables at the top:
REDIS_CONTAINER="redis" # name or ID of the Redis container
WAIT_TIMEOUT=120 # max seconds to wait for BGSAVE to finishYou need to mount the Redis data directory into the borgmatic container as a source, and include it in your borgmatic config:
# docker-compose.yml (borgmatic service)
volumes:
- /opt/docker/mystack/redis/data:/mnt/source/redis:ro# borgmatic config
source_directories:
- /mnt/source/redis # directory containing dump.rdb# borgmatic hooks
commands:
- before: everything
run:
- /borgscripts/redis-backup.shNote: If Redis is configured with
appendonly yes, the AOF file is what matters, not dump.rdb. In that case, stop the container before backup and start it again after — thedocker-stop.sh/docker-start.shpair handles this if the Redis container carries thebackuplabel.
borgmatic supports retrieving secrets from a KeePass database via the {credential keepassxc ...} syntax. Because Alpine's keepassxc package pulls in the full Qt6 GUI stack (~150 MB), this image ships an opt-in shim instead — a lightweight Python script backed by pykeepass that implements the exact CLI interface borgmatic expects.
Mount the init script at startup:
services:
borgmatic:
volumes:
- ./data/borgscripts/init-keepassxc-cli.sh:/custom-cont-init.d/init-keepassxc-cli.sh:ro
- /path/to/your/passwords.kdbx:/etc/borgmatic/passwords.kdbx:ro
- /path/to/your/passwords.keyx:/run/secrets/keepass.keyx:ro # optional key fileConfigure borgmatic:
keepassxc:
database: /etc/borgmatic/passwords.kdbx
ask_for_password: false # required for unattended/cron backups
key_file: /run/secrets/keepass.keyx
encryption_passphrase: "{credential keepassxc /etc/borgmatic/passwords.kdbx MyBorgEntry}"See data/borgscripts/KEEPASSXC-CLI.md for full setup instructions, security notes, and database hook examples.
For PostgreSQL, MariaDB/MySQL, MongoDB, and SQLite, borgmatic has built-in support that dumps the database and includes the dump in the archive — no separate script needed. Example for PostgreSQL:
postgresql_databases:
- name: mydb
hostname: db
username: postgres
password: ${POSTGRES_PASSWORD}
format: customSee the borgmatic database documentation for the full list of supported databases and options.
Mount your compose files read-only into the container as a backup source:
volumes:
- /opt/docker/mystack/docker-compose.yml:/mnt/source/docker/docker-compose.yml:ro
- /opt/docker/mystack/.env:/mnt/source/docker/.env:roThen include /mnt/source/docker in your source_directories. This ensures your Docker stack definition is included in every backup alongside your data.
