VPS Snaps
← All articles

How to Back Up MySQL and MariaDB in Docker

How to Back Up MySQL and MariaDB in Docker

Administrator running a Docker database backup

The reliable way to back up MySQL or MariaDB in Docker is to run the dump tool inside the container with docker exec, write the output to storage outside the container, and then prove you can restore it. Copying the data volume while the database is running is tempting, but it can give you a backup that does not start. This guide covers the commands, the schedule, the retention and the restore test, for both MySQL and MariaDB.

Vpssnaps
Simplify Backups Across Servers
VPS Snaps automates encrypted, S3-compatible backups across multiple cloud providers, with run logs that track successful operations and alert to failures.
Register

TL;DR

  • Use docker exec with mysqldump (MySQL) or mariadb-dump (MariaDB) and redirect the output to a file on the host, never inside the container’s writable layer.
  • Prefer a logical dump over copying a live volume: a volume copy can capture a torn state and may fail to restore.
  • Schedule dumps with cron or a systemd timer, keep copies off the server, and delete old ones with a retention rule.
  • A backup you have never restored is a guess. Restore into a throwaway container on a schedule and run a check.
  • For multiple servers, automate the whole chain (dump, upload, retention, alerts) instead of maintaining per-host scripts.

Table of Contents

Dump or volume copy: which one to use

Most MySQL containers keep data in a named volume or bind mount at /var/lib/mysql. That makes a file-level copy look easy: stop nothing, tar the directory, done. The problem is consistency.

A copy of the volume taken while the database is running can capture a torn state, and restoring it may work, need crash recovery, or fail outright. A SQL dump avoids that because the database writes out a consistent snapshot itself. The same guide makes the point that you should back up the database, not the container.

Logical dump (mysqldump / mariadb-dump) Live volume copy Physical backup tool
Consistency while running Yes, with the right flags Not guaranteed Yes, designed for it
Restore across versions Usually easier Fragile Same version family
Speed on large databases Slower to dump and restore Fast to copy Fast
Point-in-time recovery With binary logs alongside No With binary logs alongside
Complexity Low Low, but risky Medium

For most small and mid-sized databases, the dump is the right default. When the database grows into the hundreds of gigabytes and restore time matters, look at physical tools such as XtraBackup for MySQL and MariaDB. If you do copy volumes for other services, shut the container down first or use a method that backs up Docker volumes safely.

Back up MySQL in a Docker container with mysqldump

The official mysql image ships the client tools, so you can run the dump inside the running container. The Docker Official Image page documents the image, its root password variable and how to run client commands against it.

A basic one-database dump, written to the host:

docker exec db sh -c 'exec mysqldump --single-transaction -u root -p"$MYSQL_ROOT_PASSWORD" mydb' > mydb-$(date +%F).sql

Three details matter here:

  1. Single quotes around the inner command. The $MYSQL_ROOT_PASSWORD variable is then expanded inside the container, where it already exists, so the password never appears in your shell history or in ps output on the host.
  2. The redirect happens on the host. The file lands in your current directory, outside the container. If you redirect inside the container, the dump disappears when the container is removed.
  3. --single-transaction. For InnoDB tables it takes the dump inside one transaction so it reflects a single point in time without locking writes. It does nothing useful for MyISAM tables, so check your engines. The mysqldump reference lists this and the other options.

For every database on the instance, swap mydb for --all-databases, and add --routines --triggers --events if you use stored routines, triggers or scheduled events, so they are part of the dump. Our own walkthrough of MySQL and MariaDB backups with mysqldump goes through the flags in more detail.

Some teams follow a different pattern: run the dump into a temporary file inside the container, copy it out with docker cp, then delete the temporary file. Apidog’s self-hosting backup script uses exactly this approach. It works, but the direct redirect is simpler and leaves nothing behind to clean up.

Compress while you dump

SQL text compresses well. Pipe through gzip on the host:

docker exec db sh -c 'exec mysqldump --single-transaction -u root -p"$MYSQL_ROOT_PASSWORD" --all-databases' | gzip > all-$(date +%F).sql.gz

If your shell does not fail on a broken pipe, add set -o pipefail in a script. Without it, a failed dump piped into gzip still produces a valid, tiny, useless .gz file and an exit code of zero.

Dump compression pipeline and failure outcome

Back up MariaDB in Docker

MariaDB works the same way, with one naming difference. Current MariaDB images use mariadb-dump and MARIADB_ROOT_PASSWORD. MariaDB’s container backup documentation shows mariadb-dump --all-databases run through docker exec in the official image, and also covers physical backups with mariadb-backup.

docker exec mariadb sh -c 'exec mariadb-dump --single-transaction --all-databases -u root -p"$MARIADB_ROOT_PASSWORD"' > mariadb-all-$(date +%F).sql

Older images and older MariaDB versions only ship mysqldump. If the command is not found, that is why. Check which binaries your image has before you write the script.

Match versions on restore

A dump from one version restores most reliably into the same or a newer version of the same server family. Restoring a MariaDB dump into MySQL, or the reverse, can fail on syntax and on features one side does not have. Record the server version next to each backup (a one-line SELECT VERSION() output saved beside the dump is enough) and pin the image tag in your Compose file instead of using latest. Then your restore test uses the same version as production.

Restore a dump into a container

Restoring is the mirror image. Feed the file into the client inside the container with -i so standard input is passed through:

cat mydb-2026-10-26.sql | docker exec -i db sh -c 'exec mysql -u root -p"$MYSQL_ROOT_PASSWORD" mydb'

For a gzip file, use gunzip -c file.gz | docker exec -i .... For MariaDB, use the mariadb client in the same way.

Common restore failures:

  • Database does not exist. A single-database dump does not create the database unless you passed --databases. Create it first.
  • No -i flag. Without it, docker exec does not read your pipe and the restore silently does nothing.
  • Wrong credentials. Dumps do not contain user accounts unless you dumped the mysql system schema. Recreate users and grants separately.
  • Character set mismatches. Keep the same server defaults between source and target.

Automate and schedule the backups

A manual command is a backup only on the day you remember it. Put it in a script, and run the script on a timer.

#!/usr/bin/env bash
set -euo pipefail
DEST=/var/backups/mysql
STAMP=$(date +%F-%H%M)
mkdir -p "$DEST"
docker exec db sh -c 'exec mysqldump --single-transaction --routines --triggers --events -u root -p"$MYSQL_ROOT_PASSWORD" --all-databases' | gzip > "$DEST/all-$STAMP.sql.gz"
find "$DEST" -name 'all-*.sql.gz' -mtime +14 -delete

Then a crontab line to run it nightly at 2:15 a.m.:

15 2 * * * /usr/local/bin/mysql-backup.sh >> /var/log/mysql-backup.log 2>&1

Choosing the frequency

The schedule should follow how much data you can afford to lose, not a habit. The MySQL manual stresses scheduling backups regularly, and for point-in-time recovery it pairs full logical dumps with binary logs.

  • Data that changes rarely (a CMS, an internal tool): one nightly dump is usually enough.
  • Transactional data (orders, signups): dump nightly and keep binary logs, so you can replay to a moment just before a bad deployment or an accidental DELETE. See MySQL point-in-time recovery with binary logs.
  • Large, busy databases: consider taking the dump from a replica so production is not loaded. We cover that in backing up MySQL from a replica.

Also trigger a manual dump before every schema migration or major upgrade, whatever the schedule says.

Docker Compose

If the database lives in a Compose project, run the same docker exec against the service container (use docker compose exec -T db ..., where -T disables the pseudo-terminal that breaks cron jobs). Keep the Compose file itself in version control, since the file and the dump together rebuild the whole stack. Our guide to backing up a Docker Compose database shows the full layout.

Retention, encryption and off-site copies

A backup on the same disk as the database protects you from a bad query, not from a dead disk, a deleted server or ransomware.

  • Retention. A common starting point is daily copies for two weeks, weekly for two months and monthly for a year, adjusted to your compliance needs. Our page on how long to keep backups explains how to pick the numbers.
  • Off-site storage. Send each dump to an S3-compatible bucket in a different account or provider than the server. A DigitalOcean Spaces setup is one example.
  • Encryption. Dumps contain every row, including personal data and password hashes. Encrypt them before upload with GPG or age; see how to encrypt backups on Linux.
  • Tamper resistance. If an attacker gets your server, they can reach whatever credentials the server holds. Write-once storage helps, and this explanation of how immutable backups work covers the idea. Also read our notes on protecting backups from ransomware.

Test your restores

The most common backup failure is not a missing file. It is a file that exists, has a recent timestamp and cannot be loaded. A typical verification design uses a dedicated job that restores into a temporary container, runs integrity checks such as mysqlcheck, and alerts on failure.

A minimal version you can run weekly:

  1. Start a throwaway container from the same image tag as production.
  2. Pipe the latest dump into it.
  3. Run mysqlcheck --all-databases and a few row-count queries on tables you know should not be empty.
  4. Remove the container and record pass or fail.

Also alert on the absence of a backup, not only on errors: a cron job that silently stopped running produces no error at all. Our notes on knowing when a server backup fails cover that.

Our perspective

We see the same pattern with teams running MySQL or MariaDB in containers: the dump command is never the problem. The trouble starts around it. One server has a cron script, another has a different one, a third has a volume tarball from last spring, and nobody can say which of them last succeeded or whether the credentials sit in plain text in a shell file.

That is why we built VPS Snaps around a few principles. Backup data should stream straight into a bucket you own, with no staging copy in a vendor’s storage. Credentials should be stored encrypted. Every run should leave a log, so a success is a recorded fact and a failure is an alert. And you should be able to restore without us: your artifacts stay in your account, so there is no proprietary restore tool between you and your data.

If you manage one small server, the script above is enough. Once you manage several servers across providers, the cost of maintaining and checking scripts is what you are really paying for.

— Scott

Automating this across servers with VPS Snaps

VPS Snaps handles database dumps, Docker volume backups, file backups and provider snapshots on a schedule, streams the output into your own S3-compatible bucket and manages retention for you. Run logs show every successful run and flag failures. See how Docker backups work, or check the pricing page: there is a free plan at $0 per month, and paid plans run from Starter at $10 per month (or $120 per year) through Standard at $30 per month, Pro at $57 per month and Agency at $117 per month.

Best-practice checklist

  • Dump with --single-transaction for InnoDB, and include routines, triggers and events if you use them.
  • Redirect output to the host, never into the container’s own filesystem.
  • Pin image tags so the restore environment matches production.
  • Compress, encrypt, and copy off the server to a bucket under separate credentials.
  • Apply a retention rule so old dumps are deleted automatically.
  • Keep binary logs if you need point-in-time recovery.
  • Restore into a scratch container on a schedule and alert on failure or silence.

FAQ

Can I just copy /var/lib/mysql from the volume?

Only safely if the database is stopped, or if you use a tool built for physical backups. A copy taken from a running database can capture a torn state, and the restore may need crash recovery or fail. A logical dump is the simpler, safer default.

Does mysqldump lock my database in a container?

Not for InnoDB tables when you use --single-transaction, which takes a consistent snapshot without blocking writes. MyISAM tables are not covered by that option and may need locking. Check your table engines before you rely on it.

What is the difference between mysqldump and mariadb-dump?

They are the same tool lineage. Current MariaDB images name it mariadb-dump, and older ones only ship mysqldump. The usage and most options are the same, so the commands in this guide work with either name.

How often should I back up MariaDB or MySQL in Docker?

Match it to how much data you can lose. A nightly dump suits slowly changing data, and busy transactional databases should add binary logs for point-in-time recovery. Always take an extra dump before a migration or upgrade.

Sources