Back Up and Restore MariaDB in Docker Compose
Use mariadb-dump the practical way, keep copies somewhere safer than the live VPS, and verify restores before you trust the backup routine.
How to create logical MariaDB backups from a Compose stack, copy them off the server, restore them into a test database, and avoid common mistakes.
WordPress, internal tools, small SaaS apps, and self-hosted services that keep important state in one MariaDB database.
A dump file that exists but has never been restore-tested is not enough. Recovery confidence comes from proving the restore path.
Before you begin
- A Docker Compose stack with a MariaDB service you can identify by name.
- Database credentials with permission to dump and restore the target database.
- Enough free disk space for at least one full dump file.
- A restore target that is not your live production database.
For many small self-hosted services, logical backups are the best first step. They are easier to understand than filesystem-level database snapshots, they move cleanly between hosts, and they work well with common automation tools. The critical habit is not just generating the dump, but verifying you can restore it somewhere safe.
Step 1: Choose a practical backup pattern
The simplest reliable pattern for beginners is a logical dump using mariadb-dump from inside the running database container. That avoids version mismatch problems between host tools and container tools.
A typical Compose service might look like this:
services:
db:
image: mariadb:11
environment:
MARIADB_DATABASE: myapp
MARIADB_USER: myapp
MARIADB_PASSWORD: strongpassword
MARIADB_ROOT_PASSWORD: strongerrootpassword
volumes:
- db-data:/var/lib/mysqlFor a single app database, a logical dump is usually enough. If your database is very large or you need point-in-time recovery, you may eventually need a more advanced strategy. For many VPS operators, though, a verified logical dump is already a major improvement.
Step 2: Create the backup and verify the file exists
Create a host backup directory with restricted permissions:
mkdir -p ~/backups/mariadb
chmod 700 ~/backups/mariadbRun a dump from the database container to the host:
docker compose exec -T db mariadb-dump \
-u root \
-p"$MARIADB_ROOT_PASSWORD" \
--single-transaction \
--quick \
--routines \
--events \
myapp > ~/backups/mariadb/myapp_$(date +%F_%H%M).sqlIf your environment variables only exist inside the container, use a shell inside the container so the password expands there instead of on the host:
docker compose exec -T db sh -c 'mariadb-dump -u root -p"$MARIADB_ROOT_PASSWORD" --single-transaction --quick --routines --events myapp' \
> ~/backups/mariadb/myapp_$(date +%F_%H%M).sqlNow verify the output:
ls -lh ~/backups/mariadb
head -n 20 ~/backups/mariadb/myapp_2026-05-04_1600.sqlThe file should not be empty, and the header should clearly look like a MariaDB or MySQL dump. If the file is tiny, you may have redirected an error message instead of real SQL.
Step 3: Move the backup somewhere safer and prune old copies carefully
A backup on the same VPS helps with operator mistakes but does not protect you from server loss. Copy the dump off the server after creation. A simple example using scp:
scp ~/backups/mariadb/myapp_2026-05-04_1600.sql backup-user@backup-host:/srv/mariadb-backups/You can also use rclone to push encrypted copies to low-cost object storage.
For local retention, start simple. Keep a short rolling window until you have monitoring around the job:
find ~/backups/mariadb -name '*.sql' -mtime +14 -deleteOnly automate deletion after you have confidence that recent backups succeed and offsite copies exist. Retention mistakes are much more painful than a few extra gigabytes for a while.
Step 4: Restore into a safe test database and check real data
This is where the backup becomes trustworthy.
Create a test database inside the MariaDB container:
docker compose exec db mariadb -u root -p"$MARIADB_ROOT_PASSWORD" -e 'CREATE DATABASE myapp_restore_test;'Restore the dump into it:
cat ~/backups/mariadb/myapp_2026-05-04_1600.sql | \
docker compose exec -T db mariadb -u root -p"$MARIADB_ROOT_PASSWORD" myapp_restore_testNow verify the result:
docker compose exec db mariadb -u root -p"$MARIADB_ROOT_PASSWORD" -e 'SHOW TABLES;' myapp_restore_test
docker compose exec db mariadb -u root -p"$MARIADB_ROOT_PASSWORD" -e 'SELECT COUNT(*) FROM users;' myapp_restore_testReplace users with a table that actually matters in your app. Good verification checks include:
- Key tables exist
- Expected row counts are nonzero where appropriate
- Recent schema changes are present
- The app can connect to the restored database in a staging setup if you have one
When the test is complete, remove the test database if you do not need it:
docker compose exec db mariadb -u root -p"$MARIADB_ROOT_PASSWORD" -e 'DROP DATABASE myapp_restore_test;'Rollback and recovery notes
Creating a dump is low-risk. Restoring is where danger starts if you target the wrong database.
- Double-check the database name before every restore command.
- Use explicit test names like
myapp_restore_test. - If an actual production restore is needed, stop app writes first and make sure you also account for uploaded files or other state outside the database.
If you accidentally restored over the wrong database, stop the application immediately and assess whether you can recover from the previous known-good dump. This is exactly why restore tests should happen before the emergency day.
Step 5: Confirm the backup workflow is real, not just theoretical
By the end, you should have:
- A recent MariaDB SQL dump file
- A clear command pattern you can automate later
- An offsite copy plan
- Evidence that the dump restores into a safe test database
That is the point where you can start trusting the routine.
Troubleshooting common MariaDB backup and restore problems
The dump file is empty or tiny.
The command may have failed before redirection completed. Inspect the file contents and rerun carefully.
Access denied errors during dump.
Check the username, password source, and whether you are expanding the password on the host or inside the container.
The restore reports table already exists.
Use a fresh test database, or drop and recreate it before re-running the restore.
The SQL restores, but the app would still not fully recover.
Remember that many apps also need uploaded files, secret config, or background job state. Database backup alone may not be the whole recovery plan.
The dump takes too long or locks tables.
For InnoDB-heavy workloads, --single-transaction usually helps produce a consistent dump without a long global lock.
What to do next
Once the database backup path is proven, the next maturity step is scheduling it and monitoring for failures. Continue with How to Schedule Backups and Maintenance With systemd Timers.
