Skip to main content

Docker Backup and Restore Application — stop, archive, verify, restart

Project description

DBaRA — Docker Backup and Restore Application

CI PyPI Python

A production-ready Python CLI for backing up and restoring Docker application data directories. Stops the container, archives the data, verifies integrity, and restarts.

Features

  • change detection
  • checksums
  • retry logic
  • hooks
  • per-app locking

Requirements

  • Linux Desktop/Server OS
  • Python 3.11+
  • Docker in PATH
  • zstd or gzip (zstd recommended)
  • xxh128sum (optional — falls back to sha256sum when absent)

Installation

From PyPI (recommended)

pipx install dbara

Or into an isolated virtualenv:

python3 -m venv /opt/dbara
/opt/dbara/bin/pip install dbara
ln -s /opt/dbara/bin/dbara /usr/local/bin/dbara

From source

git clone https://github.com/NASible/DBaRA.git
cd DBaRA
pipx install .

Verify the install:

dbara --version

Examples

Backup — single app

Stop the container, archive the data directory, verify the checksum, then restart:

dbara -m backup \
  -a jellyfin \
  -d /srv/apps \
  -b /srv/backups \
  --keep-last 7 \
  -v

--keep-last 7 automatically prunes archives older than the 7 most recent.
-v enables debug output. Use -vv to also write a logfile.

Backup — all apps

Iterate every subdirectory under --app-folder-dir, skipping apps whose contents have not changed since the last backup:

dbara -m backup \
  -A \
  -d /srv/apps \
  -b /srv/backups \
  -p prod \
  --keep-last 7

-p prod inserts prod into every archive filename: myhost_prod_jellyfin_20240615_020000.bkup.tar.zst

Backup — force even when unchanged

dbara -m backup -a sonarr -d /srv/apps -b /srv/backups -f

-f / --force bypasses change detection and always creates a new archive.


Restore — single app (latest archive)

Auto-selects the most recent archive for the app by modification time:

dbara -m restore \
  -a jellyfin \
  -d /srv/apps \
  -b /srv/backups \
  -r /srv/apps \
  -o 1000:1000

-o 1000:1000 runs chown -R 1000:1000 on the restored directory.

Restore — single app (specific archive)

Pass the archive path directly to --backup-dest-dir to restore a particular snapshot:

dbara -m restore \
  -a jellyfin \
  -d /srv/apps \
  -b /srv/backups/myhost_jellyfin_20240615_020000.bkup.tar.zst \
  -r /srv/apps

Restore — several specific apps

--apps takes a space-separated list and restores each one sequentially:

dbara -m restore \
  --apps authelia authentik vaultwarden \
  -d /srv/apps \
  -b /srv/backups \
  -r /srv/apps \
  --ownership-map /etc/dbara/owners.txt

Using --ownership-map instead of -o applies the right ownership per app automatically.

Restore — all apps

Auto-discovers every app present in the backup directory and restores each one. Useful after a full server rebuild:

dbara -m restore \
  -A \
  -d /srv/apps \
  -b /srv/backups \
  -r /srv/apps \
  --ownership-map /etc/dbara/owners.txt \
  -v

Note: -A for restore infers app names from archive filenames. App names that contain underscores (e.g. my_app) will not be auto-discovered — use --apps explicitly for those.

Restore — without restarting the container

dbara -m restore \
  -a sonarr \
  -d /srv/apps \
  -b /srv/backups \
  -r /srv/apps \
  -s

-s / --no-start-container extracts the data but leaves the container stopped.


Archive Filename Format

{hostname}_{prefix_}{app}_{YYYYMMDD_HHMMSS}.bkup.tar.{ext}

Examples:

Scenario Filename
No prefix, zstd myhost_jellyfin_20240615_020000.bkup.tar.zst
With prefix "prod" myhost_prod_jellyfin_20240615_020000.bkup.tar.zst
gzip compressor myhost_jellyfin_20240615_020000.bkup.tar.tgz

Checksum sidecar files sit next to the archive:

  • ...bkup.tar.zst.xxh128 (when xxh128sum is available)
  • ...bkup.tar.zst.sha256sum (fallback)

CLI Reference

Required flags

Flag Description
-m, --mode Operation: backup or restore
-d, --app-folder-dir Parent directory containing per-app folders
-b, --backup-dest-dir Backup destination directory (or specific archive file for restore)

Common options

Flag Default Description
-a, --app-name Single app to back up or restore
--apps APP [APP ...] Restore a specific list of apps (restore mode only)
-A, --all-apps Backup: every subdirectory under --app-folder-dir; Restore: every app found in --backup-dest-dir
-r, --restore-dest-dir Where to extract restored files
-o, --owner-group Set user:group ownership after restore (e.g. 1000:1000)
-p, --optional-prefix Prefix inserted in the archive filename
-x, --remove-after-backup off Delete app directory after backup (archive integrity verified first)
-s, --no-start-container off Do not restart the container after the operation
-f, --force off Force backup even when no changes are detected
--strict off Treat any warning as a fatal error
--skip-checksum off Skip checksum generation/verification
-v, --verbose -v enables debug output; -vv also writes a logfile

Performance / behaviour

Flag Default Description
--compress zstd Compressor: zstd or gzip
--zstd-opts -T0 -3 Options forwarded to zstd
--fast-hash / --no-fast-hash enabled Prefer xxh128sum over sha256sum when available
--io-nice / --no-io-nice enabled Wrap heavy I/O with ionice -c2 -n7 nice -n 19
--keep-last N 0 (disabled) Prune all but the last N archives per app
--state-dir /var/lib/app-backup/state Directory for change-detection .sig state files
--hooks-dir /etc/app-backup/hooks Root directory for per-app hook scripts
--lock-dir /var/lock Directory for per-app lock files
--ownership-map File mapping app names to user:group ownership
--stop-timeout 30 Seconds passed to docker stop -t
--retry-max 5 Maximum retry attempts for shell commands
--retry-base-sleep 2 Base sleep for exponential backoff: base ** attempt seconds
--logfile /var/log/app-backup-restore.log Log file path (written when -vv is active)

Hooks

Place executable scripts at HOOKS_DIR/<app>/<hook-name>:

/etc/app-backup/hooks/
├── jellyfin/
│   ├── pre-backup
│   ├── post-backup
│   ├── pre-restore
│   └── post-restore
└── sonarr/
    └── pre-backup

Hook scripts receive no arguments. The backup or restore is aborted if a hook exits non-zero (subject to --retry-max retries with exponential backoff).

Example pre-backup hook that flushes a database:

#!/bin/bash
sqlite3 /srv/apps/myapp/data.db "PRAGMA wal_checkpoint(FULL);"

Ownership Map

Use --ownership-map to map app names to user:group ownership without specifying -o on every restore command:

# /etc/app-backup/owners.txt
jellyfin=1000:1000
sonarr=1001:1001
radarr=1001:1001
# Lines starting with # are ignored
dbara -m restore -a jellyfin -d /srv/apps -b /srv/backups -r /srv/apps \
  --ownership-map /etc/app-backup/owners.txt

If --owner-group is also specified it takes precedence over the map file.


Change Detection

DBaRA computes a SHA-256 digest over the sorted list of (relative_path, file_size, mtime_ns) tuples for every file under the app directory. The digest is stored as a .sig file in --state-dir. If the digest is unchanged since the last run, the backup is skipped.

  • Use -f / --force to override and backup anyway.
  • State files are per-app: {state-dir}/{app}.sig.

SQLite Safe Backup

Before archiving, DBaRA automatically creates consistent hot-backup copies of every SQLite database it finds (.sqlite, .sqlite3, .db) using Python's built-in sqlite3.backup(). The copies are stored as <file>.bak alongside the originals, included in the archive, and then deleted after the tar operation completes.

This ensures the archive contains a consistent point-in-time snapshot even if the application was writing to the database at the time of backup.


Running as a Scheduled Job

systemd timer (recommended)

# /etc/systemd/system/dbara-backup.timer
[Unit]
Description=Nightly DBaRA backup

[Timer]
OnCalendar=*-*-* 02:00:00
RandomizedDelaySec=300
Persistent=true

[Install]
WantedBy=timers.target
# /etc/systemd/system/dbara-backup.service
[Unit]
Description=DBaRA backup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/dbara -m backup -A \
  -d /srv/apps -b /srv/backups \
  --keep-last 7 -vv
systemctl enable --now dbara-backup.timer

cron

0 2 * * * root /usr/local/bin/dbara -m backup -A -d /srv/apps -b /srv/backups --keep-last 7

Deployment with Ansible

An Ansible role is available in a separate repository: NASible/ansible-role-dbara

ansible-galaxy install nasible.dbara

The role installs DBaRA into a virtualenv, creates required directories, and configures a systemd timer for scheduled backups.


Development

# Install dev dependencies
python3 -m venv .venv
.venv/bin/pip install -r requirements-dev.txt -e .

# Run tests
.venv/bin/pytest

# Type-check
.venv/bin/mypy dbara

# Lint
.venv/bin/ruff check dbara tests

All tests are pure unit tests — no Docker, no real filesystem writes (except where tmp_path is used), no subprocess calls. The FakeRunner and FakeDockerClient test doubles in tests/conftest.py cover every code path without touching the system.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dbara-20260714.tar.gz (31.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dbara-20260714-py3-none-any.whl (23.0 kB view details)

Uploaded Python 3

File details

Details for the file dbara-20260714.tar.gz.

File metadata

  • Download URL: dbara-20260714.tar.gz
  • Upload date:
  • Size: 31.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for dbara-20260714.tar.gz
Algorithm Hash digest
SHA256 d90efe9e12a408d9f575638fc300c50ab83c1530e5e256d092a4acb027290553
MD5 134bd7d43f85f6778555c50bbea7cb89
BLAKE2b-256 4c254dfa09c53fb2a6857aae11c3a1be60e898bf78666527b2c5ff227b752107

See more details on using hashes here.

Provenance

The following attestation bundles were made for dbara-20260714.tar.gz:

Publisher: ci.yml on NASible/DBaRA

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dbara-20260714-py3-none-any.whl.

File metadata

  • Download URL: dbara-20260714-py3-none-any.whl
  • Upload date:
  • Size: 23.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for dbara-20260714-py3-none-any.whl
Algorithm Hash digest
SHA256 846f64486bcbeda28df37071de727fa2fb7df841f8c7911fd83346f3901362e3
MD5 2c95985820d3c53929861b89b49f4f87
BLAKE2b-256 9db2169a06b8b02f167bcafb9608c283de93999966302fa7c73291c47c091a51

See more details on using hashes here.

Provenance

The following attestation bundles were made for dbara-20260714-py3-none-any.whl:

Publisher: ci.yml on NASible/DBaRA

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page