How to Back Up a MySQL Database in Docker (MySQL 8.4)

If you’re running MySQL in Docker, you will eventually need a backup: before a risky Laravel migration, a database upgrade, a bulk update, or a cleanup script. Docker volumes keep database files across normal container recreation, but they do not undo accidental changes or replace an independent backup.

I recently needed to back up MySQL in a local Laravel/PHP development stack. My containers included:

IMAGE NAME
mysql:8.4 wishlist-mysql-1
wishlist-app wishlist-app-1
nginx wishlist-nginx-1

MySQL was published on `127.0.0.1:3307` on the host, mapped to port `3306` in the container. The database was named `wishlist`.

This tutorial covers creating SQL dumps, handling credentials, working safely in PowerShell, restoring a database, and checking whether your backup actually works.

> Scope: These examples target a local MySQL 8.4 development container. For production, plan for recovery time, encryption, off-host storage, retention, and consistent backups of all relevant systems.

1. Identify the MySQL container and database

List running containers:

docker ps

If you’re using Docker Compose, you can also list services:

docker compose ps

The examples below use `wishlist-mysql-1` as the container name and `wishlist` as the database. Substitute your own names.

Check whether the container includes the dump client:

docker exec wishlist-mysql-1 mysqldump --version

2. Why a Docker volume is not a backup

A typical Compose service might mount a named volume:

services:
mysql:
image: mysql:8.4
volumes:
- mysql_data:/var/lib/mysql

volumes:
mysql_data:

With a named volume, `docker compose down` followed by `docker compose up -d` normally preserves the database. But `docker compose down -v` removes declared named volumes, and a destructive SQL statement can damage data even when the volume remains intact.

A logical SQL dump stored outside the database volume gives you another recovery option. For important data, keep another copy off the same machine.

3. Create a backup with `docker exec` (Linux/macOS)

Run `mysqldump` inside the MySQL container and redirect its output to a file **on the host**:

docker exec -i wishlist-mysql-1 \
mysqldump -u root -p --single-transaction --quick \
--routines --events --triggers wishlist \
> wishlist_backup.sql

The `-p` flag prompts for a password. The dump goes to `wishlist_backup.sql` in your current host directory. `–single-transaction` provides a consistent snapshot for transactional tables such as InnoDB, provided concurrent schema changes are avoided; it does **not** guarantee consistency for nontransactional tables. `–quick` streams rows instead of buffering entire tables. Triggers are normally included by default, but `–triggers` makes the intent explicit; routines and events need explicit options.

Important: Avoid `docker exec -t` when exporting a SQL dump: allocating a pseudo-terminal can alter output. If your terminal or Docker setup makes password prompting unreliable, use the host-port approach below or a properly secured MySQL option file rather than embedding a password in the command.

4. Back up through the published host port

If the MySQL client tools are installed on your computer, you can connect through the published port instead of executing the client inside Docker:

mysqldump -h 127.0.0.1 -P 3307 -u root -p \
--single-transaction --quick --routines --events --triggers \
wishlist > wishlist_backup.sql

This method prompts for the password in your host terminal and avoids Docker’s interactive-input complications. Check that the host client is compatible with the server version. Keep port `3307` bound to `127.0.0.1` for local-only access when remote access is unnecessary.

5. Windows PowerShell: avoid text-encoding surprises

PowerShell output redirection has varied across versions. In particular, Windows PowerShell 5.1 may write redirected native-command output using UTF-16LE, which is unsuitable for a normal SQL dump. PowerShell 7.4 and newer preserve native stdout bytes for file redirection, but do not assume every developer is using that version.

A practical cross-version option is to let **`cmd.exe`** handle native output redirection:

cmd /c 'docker exec -i wishlist-mysql-1 mysqldump -u root --single-transaction --quick --routines --events --triggers wishlist > wishlist_backup.sql'

This example intentionally omits authentication: it only works if the MySQL client already has a valid, securely configured authentication method. Otherwise, prefer the host-port `mysqldump -p` command in a compatible shell, or use a restricted MySQL option file. Do **not** copy a password into command history just to make an automated command work.

For a simple interactive PowerShell workflow, a reliable alternative is to run the host-installed `mysqldump` with `-p` and use PowerShell 7.4+ for byte-preserving redirection:

mysqldump -h 127.0.0.1 -P 3307 -u root -p `
--single-transaction --quick --routines --events --triggers `
wishlist > wishlist_backup.sql

Check your PowerShell version with `$PSVersionTable.PSVersion`. If you use Windows PowerShell 5.1, perform the export from Command Prompt or another binary-safe shell instead.

6. Troubleshoot `Access denied … (using password: NO)`

One error I encountered was:

mysqldump: Got error: 1045: Access denied for user 'root'@'localhost'

(using password: NO) when trying to connect

The phrase `using password: NO` means the client did not send a password. It does not prove that the stored password is wrong. Verify the authentication method, the account, and whether the client actually received the password prompt.

In local development, credentials are often configured through Compose environment variables, an `.env` file, or Docker secrets. **Do not paste environment-variable output into bug reports or public logs:** it can expose passwords. Also remember that `MYSQL_ROOT_PASSWORD` in the official image is primarily an initialization setting; changing it after the data directory has been initialized does not automatically change an existing MySQL account password.

Avoid the tempting `-psecret123` syntax in published examples and scripts: command-line passwords can be exposed through process listings, shell history, and diagnostic logs. For unattended backups, use a protected MySQL client option file or another appropriate secret-management mechanism.

7. Back up all databases

For a local instance containing several databases, use:

docker exec -i wishlist-mysql-1 \
mysqldump -u root -p --all-databases \
--single-transaction --quick --routines --events --triggers \
> all_databases_backup.sql

This includes database definitions and data, plus routines, events, and triggers when the account has the required privileges. It is not a complete backup of Docker configuration, attached files, or every server-level setting. Check whether dumping the `mysql` system database is appropriate for your intended restore or migration, especially across MySQL versions.

8. Alternative: create the dump inside the container

If redirecting output on the host is inconvenient, create a file inside the container and copy it out:

docker exec -it wishlist-mysql-1 sh

Inside the container:

mysqldump -u root -p --single-transaction --quick \
--routines --events --triggers wishlist \
> /tmp/wishlist_backup.sql
exit

Then on the host:

docker cp wishlist-mysql-1:/tmp/wishlist_backup.sql ./wishlist_backup.sql

Confirm that the host copy is usable before removing the temporary file. A file left only in the container’s writable layer can disappear when the container is removed.

9. Verify the backup before relying on it

At minimum, check the command’s exit status immediately after it runs and confirm that the file exists and is nonempty. On Linux/macOS:

ls -lh wishlist_backup.sql
head -n 20 wishlist_backup.sql

On PowerShell:
[PowerShell]
Get-Item .\wishlist_backup.sql
Get-Content .\wishlist_backup.sql -TotalCount 20
[/PowerShell]

A recognizable SQL header and a plausible file size are useful sanity checks, **not proof of recoverability**. The strongest practical check is to restore the dump into a disposable test database and verify the expected tables and representative records. Do not run a restore test against your only working database.

10. Restore a backup safely

Warning: Importing a dump can replace or modify existing tables. Take a fresh backup first and preferably restore into a separate test database.

Create an empty test database (the client will prompt for the password):

docker exec -it wishlist-mysql-1 mysql -u root -p \
-e 'CREATE DATABASE wishlist_restore_test;'

On Linux/macOS, a convenient import method is to stream the dump through standard input:

docker exec -i wishlist-mysql-1 \
mysql -u root wishlist_restore_test < wishlist_backup.sql

The command above assumes a securely configured noninteractive authentication method. If that is not available, use a host-installed MySQL client and an interactive password prompt:

mysql -h 127.0.0.1 -P 3307 -u root -p \
wishlist_restore_test < wishlist_backup.sql

On Windows, run the host-client import command in **Command Prompt** (`cmd.exe`), which supports `<` input redirection. Do not use `Get-Content … | docker exec …` as a universal restore recipe: text pipelines may transform the SQL bytes, and native input-redirection behavior differs between shells.

After importing, verify the tables and data:

docker exec -it wishlist-mysql-1 mysql -u root -p \
-e 'SHOW TABLES FROM wishlist_restore_test;'

If your dump includes `CREATE DATABASE` and `USE` statements (for example, an `–all-databases` dump), it may select its own target databases. Review it before importing; specifying a database on the command line does not override every statement inside the dump.

11. Name and store backups predictably

I prefer a project name followed by a timestamp:

wishlist__2026-11-04_18-00.sql

Keep dumps in a dedicated `backups/` directory, exclude that directory from Git, and protect the files as sensitive data: development databases can still contain personal information, credentials, or API tokens.

For a manual backup, a short checklist is enough:

1. Confirm the container and database name.
2. Create a dump using the correct client and authentication method.
3. Check the exit status and output file.
4. Restore into a separate test database.
5. Store an additional copy somewhere independent of the Docker host if the data matters.

12. Automate backups carefully

For recurring backups, a scheduled script should create the destination directory, use unique filenames, fail when `mysqldump` fails, and avoid leaving a partial dump with a successful-looking filename. It should also define retention and secure credential handling. On Windows, use Task Scheduler; on Linux, use cron or a systemd timer.

For local development, automation may be optional. A verified manual backup immediately before a destructive migration is often more valuable than a scheduled job that nobody has tested restoring.

Final thoughts

Docker containers are disposable; your database state might not be. A named volume protects data across normal container recreation, while an independent SQL dump protects against a different class of mistakes.

For a Laravel, Symfony, WordPress, or other PHP development stack, `mysqldump` remains a practical way to capture and restore a MySQL database. Pay particular attention to password handling, shell-specific redirection, transactional consistency, and restoration tests. A backup is only useful when you can successfully recover from it.

Leave a Reply

Your email address will not be published. Required fields are marked *