News

Flawless Docker Backups: Safely Backing Up Databases and Persistent Volumes with Duplicati

Backing up live Docker containers without risking silent database corruption can feel like a gamble for any self-hoster. Discover how to leverage Duplicati’s lifecycle hooks to create zero-downtime, bulletproof backups for your persistent volumes, configurations, and databases.

If you run a homelab or self-host your own services, Docker feels like pure magic. With a single docker compose up -d, you can spin up complex applications in seconds. But behind that sleek containerized layer lies a harsh reality: containers are ephemeral, but your data is not.

If your server crashes, pulling Docker images again takes two minutes. Rebuilding lost application settings, user uploads, or a corrupted database? That can take days; if it’s even possible.

Backing up live Docker containers requires a bit more care than just copying folders around. In this guide, we’ll cover how to use Duplicati alongside container lifecycle hooks to create zero-downtime, non-corruptible backups for your databases, persistent volumes, and configuration files.

The Golden Rule: Never Copy a Live Database File

It is tempting to simply point Duplicati at your Docker storage directory (like /var/lib/docker/volumes or your local ./databind mounts) and call it a day.

The Risk: If a database (PostgreSQL, MariaDB, SQLite, etc.) writes to disk while Duplicati is reading the file, you end up with a dirty read. The snapshot will contain mismatched file headers and incomplete transactions, rendering your backup unrecoverable when you need it most.

To get clean backups without taking your entire stack offline, you need to dump or freeze your database state right before Duplicati scans your storage.

Step 1: Use run-script-before and run-script-after Hooks

Duplicati features powerful lifecycle parameters: --run-script-before and --run-script-after. These allow you to execute custom Shell or PowerShell scripts on the host system before a backup job starts and after it finishes.

1. The Pre-Backup Script (pre-backup.sh)

This script tells Docker to dump active databases into a dedicated backup folder before Duplicati begins archiving.

Create a script on your host machine (e.g., /opt/duplicati/scripts/pre-backup.sh):


#!/bin/bash
# Exit immediately if a command fails
set -e

# Path where database dumps will be temporarily stored
DUMP_DIR="/opt/docker-backups/dumps"
mkdir -p "$DUMP_DIR"

echo "Starting pre-backup database dumps..."

# 1. PostgreSQL Container Dump
docker exec -t postgres_container_name pg_dumpall -U postgres > "$DUMP_DIR/postgres_all.sql"

# 2. MariaDB / MySQL Container Dump
docker exec -t mariadb_container_name mariadb-dump -u root -p'YourRootPassword' --all-databases > "$DUMP_DIR/mariadb_all.sql"

# 3. SQLite (e.g., Vaultwarden or Home Assistant)
# For SQLite, use the built-in backup API to safely copy the DB while live
docker exec -t vaultwarden_container sqlite3 /data/db.sqlite3 ".backup '$DUMP_DIR/vaultwarden.sqlite3'"

echo "All database dumps completed successfully!"
exit 0
#!/bin/bash
# Exit immediately if a command fails
set -e

# Path where database dumps will be temporarily stored
DUMP_DIR="/opt/docker-backups/dumps"
mkdir -p "$DUMP_DIR"

echo "Starting pre-backup database dumps..."

# 1. PostgreSQL Container Dump
docker exec -t postgres_container_name pg_dumpall -U postgres > "$DUMP_DIR/postgres_all.sql"

# 2. MariaDB / MySQL Container Dump
docker exec -t mariadb_container_name mariadb-dump -u root -p'YourRootPassword' --all-databases > "$DUMP_DIR/mariadb_all.sql"

# 3. SQLite (e.g., Vaultwarden or Home Assistant)
# For SQLite, use the built-in backup API to safely copy the DB while live
docker exec -t vaultwarden_container sqlite3 /data/db.sqlite3 ".backup '$DUMP_DIR/vaultwarden.sqlite3'"

echo "All database dumps completed successfully!"
exit 0
#!/bin/bash
# Exit immediately if a command fails
set -e

# Path where database dumps will be temporarily stored
DUMP_DIR="/opt/docker-backups/dumps"
mkdir -p "$DUMP_DIR"

echo "Starting pre-backup database dumps..."

# 1. PostgreSQL Container Dump
docker exec -t postgres_container_name pg_dumpall -U postgres > "$DUMP_DIR/postgres_all.sql"

# 2. MariaDB / MySQL Container Dump
docker exec -t mariadb_container_name mariadb-dump -u root -p'YourRootPassword' --all-databases > "$DUMP_DIR/mariadb_all.sql"

# 3. SQLite (e.g., Vaultwarden or Home Assistant)
# For SQLite, use the built-in backup API to safely copy the DB while live
docker exec -t vaultwarden_container sqlite3 /data/db.sqlite3 ".backup '$DUMP_DIR/vaultwarden.sqlite3'"

echo "All database dumps completed successfully!"
exit 0

2. The Post-Backup Script (post-backup.sh)

Once Duplicati finishes encrypting and uploading your snapshot to your backup target (B2, S3, NAS, SFTP, etc.), the post-script cleans up the temporary SQL dumps to save local disk space.

Create /opt/duplicati/scripts/post-backup.sh:

#!/bin/bash

DUMP_DIR="/opt/docker-backups/dumps"

echo "Cleaning up temporary database dumps..."
rm -rf "$DUMP_DIR"/*.sql
rm -rf "$DUMP_DIR"/*.sqlite3

echo "Cleanup finished."
exit 0
#!/bin/bash

DUMP_DIR="/opt/docker-backups/dumps"

echo "Cleaning up temporary database dumps..."
rm -rf "$DUMP_DIR"/*.sql
rm -rf "$DUMP_DIR"/*.sqlite3

echo "Cleanup finished."
exit 0
#!/bin/bash

DUMP_DIR="/opt/docker-backups/dumps"

echo "Cleaning up temporary database dumps..."
rm -rf "$DUMP_DIR"/*.sql
rm -rf "$DUMP_DIR"/*.sqlite3

echo "Cleanup finished."
exit 0

3. Connecting the Scripts to Duplicati

In the Duplicati Web UI:

  1. Navigate to your backup job edit screen.

  2. Go to Step 5: Options -> Advanced options.

  3. Add the following options:

    • run-script-before = /opt/duplicati/scripts/pre-backup.sh

    • run-script-after = /opt/duplicati/scripts/post-backup.sh

    • run-script-timeout = 10m (Adjust based on how long your dumps take)

Step 2: Structure Your Backup Path Blueprint

For a full application restore, database dumps aren't enough. You need the exact configuration files, environment variables, and persistent media volumes that make your stack tick.

Organize your homelab directory structure on the host cleanly so Duplicati can ingest everything in one pass:

/opt/
├── docker-apps/
├── nextcloud/
├── docker-compose.yml
├── .env
└── data/               <-- Persistent bind mount (media/uploads)
│   ├── vaultwarden/
│   │   ├── docker-compose.yml
│   │   └── data/               <-- Persistent config files
└── docker-backups/
    └── dumps/                  <

/opt/
├── docker-apps/
├── nextcloud/
├── docker-compose.yml
├── .env
└── data/               <-- Persistent bind mount (media/uploads)
│   ├── vaultwarden/
│   │   ├── docker-compose.yml
│   │   └── data/               <-- Persistent config files
└── docker-backups/
    └── dumps/                  <

/opt/
├── docker-apps/
├── nextcloud/
├── docker-compose.yml
├── .env
└── data/               <-- Persistent bind mount (media/uploads)
│   ├── vaultwarden/
│   │   ├── docker-compose.yml
│   │   └── data/               <-- Persistent config files
└── docker-backups/
    └── dumps/                  <

Target Source Paths in Duplicati

In Step 3: Source Data of your Duplicati backup config, select the following:

  • /opt/docker-apps/ (Captures all compose files, .env secret files, and static volume data)

  • /opt/docker-backups/dumps/ (Captures the fresh SQL/SQLite dumps created by your script)

Step 3: Best Practices for Automated Zero-Downtime Backups

To ensure your setup runs smoothly in the background without manual intervention, keep these best practices in mind:

1. Off-Peak Scheduling

Set your Duplicati job to run automatically once per day during off-peak hours (e.g., 3:00 AM). Because the pre-script dumps the database in milliseconds or seconds depending on dataset size, your services experience zero downtime.

2. Use Retention Policies Smartly

Database dumps and persistent volumes compress exceptionally well. Take advantage of Duplicati’s built-in Smart Backup Retention:

  • Keep 1 backup per day for the last 7 days.

  • Keep 1 backup per week for the last 4 weeks.

  • Keep 1 backup per month for the last 12 months.

This gives you deep historical point-in-time recovery options without blowing up your storage destination quota.

3. Always Test Disaster Recovery

A backup system you haven't tested is just a hallucination. Every few months, test your system:

  1. Spin up a clean test VM or secondary machine.

  2. Restore your Duplicati backup folder.

  3. Import the SQL dump into a test container (docker exec -i test_db psql -U postgres < postgres_all.sql).

  4. Run docker compose up -d and verify the app comes back up seamlessly.

Summary

By combining Docker’s CLI tools with Duplicati’s pre/post script hooks, you get the best of both worlds: consistent, uncorrupted database snapshots and light-speed file uploads, all running in the background without taking your services offline.

Ready to start? Visit the download page to get Duplicati today. And yes, you can run Duplicati in Docker as well!

Get started for free

Pick your own backend and store encrypted backups of your files anywhere online or offline. For MacOS, Windows and Linux.

Pick your own backend and store encrypted backups of your files anywhere online or offline. For MacOS, Windows and Linux.

  • Example image