Kevin Veen-BirkenbachandClaude Opus 5 2129c5e362 build(lint): gate make test on a clean ruff run
ruff was never wired into this repository: no target, no CI step, no pin.
It reported 45 findings across sources and tests, so nothing enforced
what the codebase already mostly followed.

Adds `make ruff` (check + format --check), `make ruff-fix`, and `make
lint` as its alias, and makes `make test` run lint as a fourth parallel
spur. The CI workflow calls `make test`, so it is covered there too. The
linter is pinned in a `lint` extra: a ruff minor bump changes which rules
fire, and with the suite gating on a clean run an unpinned linter would
fail it on an unrelated day.

The 45 findings are fixed rather than configured away. Three needed a
decision instead of the mechanical fix:

- The generation timestamp keeps its local wall clock (DTZ005 waived).
  Generations sort by that name, and UTC would order new ones before the
  existing ones wherever the offset is positive - "newest generation" is
  what every restore path selects on.
- The per-volume `copy` closure now binds volume_name and vol_dir as
  default arguments (B023). It only worked because it is called inside
  the same iteration.
- The two CLI top-level handlers keep their blind except (BLE001
  waived): turning any failure into exit 1 is what a CLI boundary is
  for. The two in run.py did not need it and were narrowed to what they
  actually catch.

Also drops the comments that restate the code: the section banners in
restore/__main__.py, the filename repeated as line 1 of nine test files,
step narration above the statement it narrates, and a block in app.py
documenting parameters that had moved to another module. What names a
trip-wire stays - the snapshot destination rule, the mysql-binary
absence in MariaDB 11 images, the session-scoped FOREIGN_KEY_CHECKS, the
spooled temp file for multi-GB dumps, and the negative control that
loses its discriminating power if it ever passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 04:35:50 +02:00
2026-08-16 13:57:49 +02:00
2020-10-11 11:54:16 +02:00
2025-12-27 12:49:24 +01:00

baudolo – Deterministic Backup & Restore for Docker Volumes 📦🔄

GitHub Sponsors Patreon Buy Me a Coffee PayPal License: AGPL v3 Docker Version Python Version GitHub stars

baudolo is a backup and restore system for Docker volumes with mandatory file backups and explicit, deterministic database dumps. It is designed for environments with many Docker services where:

  • file-level backups must always exist
  • database dumps must be intentional, predictable, and auditable

✨ Key Features

  • 📦 Incremental Docker volume backups using rsync --link-dest
  • 🗄 Optional SQL dumps for:
    • PostgreSQL
    • MariaDB / MySQL
  • 🌱 Explicit database definition for SQL backups (no auto-discovery)
  • 🧾 Backup integrity stamping via dirval (Python API)
  • ⏸ Automatic container stop/start when required for consistency
  • 🚫 Whitelisting of containers that do not require stopping
  • ♻️ Modular, maintainable Python architecture

🧠 Core Concept (Important!)

baudolo separates file backups from database dumps.

  • Docker volumes are always backed up at file level
  • SQL dumps are created only for explicitly defined databases

This results in the following behavior:

Database defined File backup SQL dump
No ✔ yes ✘ no
Yes ✔ yes ✔ yes

📁 Backup Layout

Backups are stored in a deterministic, fully nested structure:

<backups-dir>/
└── <machine-hash>/
    └── <repo-name>/
        └── <timestamp>/
            └── <volume-name>/
                ├── files/
                └── sql/
                    └── <database>.backup.sql

Meaning of each level

  • <machine-hash> SHA256 hash of /etc/machine-id (host separation)

  • <repo-name> Logical backup namespace (project / stack)

  • <timestamp> Backup generation (YYYYMMDDHHMMSS)

  • <volume-name> Docker volume name

  • files/ Incremental file backup (rsync)

  • sql/ Optional SQL dumps (only for defined databases)

🚀 Installation

Local (editable install)

python3 -m venv .venv
source .venv/bin/activate
pip install -e .

🌱 Database Definition (SQL Backup Scope)

How SQL backups are defined

baudolo creates SQL dumps only for databases that are explicitly defined via configuration (e.g. a databases definition file or seeding step).

If a database is not defined:

  • its Docker volume is still backed up (files)
  • no SQL dump is created

No database definition → file backup only Database definition present → file backup + SQL dump

Why explicit definition?

baudolo does not inspect running containers to guess databases.

Databases must be explicitly defined to guarantee:

  • deterministic backups
  • predictable restore behavior
  • reproducible environments
  • zero accidental production data exposure

Required database metadata

Each database definition provides:

  • database instance (container or logical instance)
  • database name
  • database user
  • database password

This information is used by baudolo to execute pg_dump, pg_dumpall, or mariadb-dump.

💾 Running a Backup

baudolo \
  --compose-dir /srv/docker \
  --databases-csv /etc/baudolo/databases.csv \
  --database-containers central-postgres central-mariadb \
  --images-no-stop-required alpine postgres mariadb mysql \
  --images-no-backup-required redis busybox

Common Backup Flags

Flag Description
--everything Always stop containers and re-run rsync
--dump-only-sql Skip file backups only for DB volumes when dumps succeed; non-DB volumes are still backed up; fallback to files if no dump.
--shutdown Do not restart containers after backup
--backups-dir Backup root directory (default: /Backups)
--repo-name Backup namespace under machine hash

♻️ Restore Operations

Restore Volume Files

baudolo-restore files \
  my-volume \
  <machine-hash> \
  <version> \
  --backups-dir /Backups \
  --repo-name my-repo

Restore into a different target volume:

baudolo-restore files \
  target-volume \
  <machine-hash> \
  <version> \
  --source-volume source-volume

Restore PostgreSQL

baudolo-restore postgres \
  my-volume \
  <machine-hash> \
  <version> \
  --container postgres \
  --db-name appdb \
  --db-password secret \
  --empty

Restore MariaDB / MySQL

baudolo-restore mariadb \
  my-volume \
  <machine-hash> \
  <version> \
  --container mariadb \
  --db-name shopdb \
  --db-password secret \
  --empty

baudolo automatically detects whether mariadb or mysql is available inside the container

🔍 Backup Scheme

The backup mechanism uses incremental backups with rsync and stamps directories with a unique hash. For more details on the backup scheme, check out this blog post.
Backup Scheme

👨‍💻 Author

Kevin Veen-Birkenbach

📜 License

This project is licensed under the GNU Affero General Public License v3.0. See the LICENSE file for details.

🔗 More Information


Happy Backing Up! 🚀🔐

S
Description
Backup Docker Volumes to Local is a comprehensive solution that leverages rsync to create incremental backups of Docker volumes, providing seamless recovery for both file and database data. Ideal for ensuring the integrity and security of your container data.
Readme AGPL-3.0
943 KiB
0 Stars 1 Watchers 0 Forks
Languages
Python 97%
Shell 2%
Makefile 0.7%
Dockerfile 0.3%