Contents
Run MySQL in Docker: Setup, Connections, Backup and Restore
Start it with one command
First create a folder mysql-demo. Its initdb/ folder holds two init SQL files (explained below):
mysql-demo/
└── initdb/
├── 01-schema.sql
└── 02-seed.sql
initdb/01-schema.sql:
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price INT NOT NULL
);
initdb/02-seed.sql:
INSERT INTO products (name, price) VALUES ('珍珠奶茶', 65), ('鳳梨酥', 45), ('牛肉麵', 180);
Start MySQL from the mysql-demo folder:
docker run -d --name mysql \
-e MYSQL_ROOT_PASSWORD=secret \
-e MYSQL_DATABASE=app \
-e MYSQL_USER=app \
-e MYSQL_PASSWORD=apppass \
-p 127.0.0.1:3306:3306 \
-v mysql_data:/var/lib/mysql \
-v "$(pwd)/initdb:/docker-entrypoint-initdb.d:ro" \
--restart unless-stopped \
mysql:8.4
docker run -d --name mysql `
-e MYSQL_ROOT_PASSWORD=secret `
-e MYSQL_DATABASE=app `
-e MYSQL_USER=app `
-e MYSQL_PASSWORD=apppass `
-p 127.0.0.1:3306:3306 `
-v mysql_data:/var/lib/mysql `
-v "${PWD}/initdb:/docker-entrypoint-initdb.d:ro" `
--restart unless-stopped `
mysql:8.4
docker run -d --name mysql ^
-e MYSQL_ROOT_PASSWORD=secret ^
-e MYSQL_DATABASE=app ^
-e MYSQL_USER=app ^
-e MYSQL_PASSWORD=apppass ^
-p 127.0.0.1:3306:3306 ^
-v mysql_data:/var/lib/mysql ^
-v "%cd%/initdb:/docker-entrypoint-initdb.d:ro" ^
--restart unless-stopped ^
mysql:8.4
$ docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=app -e MYSQL_USER=app -e MYSQL_PASSWORD=apppass -p 127.0.0.1:3306:3306 -v mysql_data:/var/lib/mysql -v "$(pwd)/initdb:/docker-entrypoint-initdb.d:ro" --restart unless-stopped mysql:8.4
Unable to find image 'mysql:8.4' locally
8.4: Pulling from library/mysql
e896da32f635: Pull complete
3527249a8025: Pull complete
6b54d15c954d: Pull complete
15ba404f5528: Pull complete
ad73434ffdad: Pull complete
bf11ec3e7e56: Pull complete
81077ec0e5ac: Pull complete
f51a9a1f0d20: Pull complete
4ba189089539: Pull complete
d4cc7515d7b7: Pull complete
Digest: sha256:24989179f58856db763f418010c1169fecc944e3ec1ea7e921572d83eb070f70
Status: Downloaded newer image for mysql:8.4
d92a7561ad58f958488c606bb07753aeffcc06639878a4c87ed676f6e1f99389
Windows versions (click a tab to switch):
docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=app -e MYSQL_USER=app -e MYSQL_PASSWORD=apppass -p 127.0.0.1:3306:3306 -v mysql_data:/var/lib/mysql -v "$(pwd)/initdb:/docker-entrypoint-initdb.d:ro" --restart unless-stopped mysql:8.4
docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=app -e MYSQL_USER=app -e MYSQL_PASSWORD=apppass -p 127.0.0.1:3306:3306 -v mysql_data:/var/lib/mysql -v "${PWD}/initdb:/docker-entrypoint-initdb.d:ro" --restart unless-stopped mysql:8.4
docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=app -e MYSQL_USER=app -e MYSQL_PASSWORD=apppass -p 127.0.0.1:3306:3306 -v mysql_data:/var/lib/mysql -v "%cd%/initdb:/docker-entrypoint-initdb.d:ro" --restart unless-stopped mysql:8.4
| Option | Description |
|---|---|
MYSQL_ROOT_PASSWORD | root password (required, or use MYSQL_RANDOM_ROOT_PASSWORD=yes to generate a random one) |
MYSQL_DATABASE | Database created on first start |
MYSQL_USER / MYSQL_PASSWORD | Creates a regular user with full privileges on MYSQL_DATABASE |
-p 127.0.0.1:3306:3306 | Only reachable from this machine, not from outside |
-v mysql_data:/var/lib/mysql | Data is stored in a volume named mysql_data |
-v .../initdb:/docker-entrypoint-initdb.d:ro | SQL to run during initialization |
What happens on the first start
$ docker logs mysql 2>&1 | grep -E "Entrypoint|initdb|ready for connections"
2026-10-13 00:46:59+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.4.11-1.el9 started.
2026-10-13 00:46:59+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
2026-10-13 00:46:59+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.4.11-1.el9 started.
2026-10-13 00:46:59+00:00 [Note] [Entrypoint]: Initializing database files
2026-10-13 00:47:02+00:00 [Note] [Entrypoint]: Database files initialized
2026-10-13 00:47:02+00:00 [Note] [Entrypoint]: Starting temporary server
2026-10-13T00:47:04.184489Z 0 [System] [MY-011323] [Server] X Plugin ready for connections. Socket: /var/run/mysqld/mysqlx.sock
2026-10-13T00:47:04.185012Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.4.11' socket: '/var/run/mysqld/mysqld.sock' port: 0 MySQL Community Server - GPL.
2026-10-13 00:47:04+00:00 [Note] [Entrypoint]: Temporary server started.
2026-10-13 00:47:06+00:00 [Note] [Entrypoint]: Creating database app
2026-10-13 00:47:06+00:00 [Note] [Entrypoint]: Creating user app
2026-10-13 00:47:06+00:00 [Note] [Entrypoint]: Giving user app access to schema app
2026-10-13 00:47:06+00:00 [Note] [Entrypoint]: /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/01-schema.sql
2026-10-13 00:47:06+00:00 [Note] [Entrypoint]: /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/02-seed.sql
2026-10-13 00:47:06+00:00 [Note] [Entrypoint]: Stopping temporary server
2026-10-13 00:47:07+00:00 [Note] [Entrypoint]: Temporary server stopped
2026-10-13 00:47:07+00:00 [Note] [Entrypoint]: MySQL init process done. Ready for start up.
2026-10-13T00:47:08.596982Z 0 [System] [MY-011323] [Server] X Plugin ready for connections. Bind-address: '::' port: 33060, socket: /var/run/mysqld/mysqlx.sock
2026-10-13T00:47:08.597330Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.4.11' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server - GPL.
The log shows that on the first start (when the data folder is empty) the image's startup script:
- Creates the database files (
Initializing database files). - Starts a temporary server (
Starting temporary server). Note itsport: 0: the temporary server accepts no network connections, only socket connections. - Creates the database
appand the userapp, and grants privileges. - Runs
01-schema.sqland02-seed.sqlfrom/docker-entrypoint-initdb.d/in file name order. - Stops the temporary server and starts with the real configuration. Only the last line, with
port: 3306, means you can actually connect.
The whole process takes about 10 seconds. /docker-entrypoint-initdb.d/ accepts .sql, .sql.gz and .sh files, and they run only on the first initialization; restarting the container later does not run them again.
Choosing a version
| tag | Description |
|---|---|
mysql:8.4 | Long-term support (LTS) release, recommended for production |
mysql:9 | Innovation release: newer features, shorter support period |
mariadb:11 | MariaDB, a highly compatible fork of MySQL; environment variables start with MARIADB_ |
Connecting
From inside the container
The container already includes the mysql client. For interactive use, run docker exec -it mysql mysql -uapp -p, enter the password and you get the mysql> prompt. You can also run SQL directly with -e:
$ docker exec mysql mysql -uapp -papppass app -t -e "SELECT * FROM products;"
mysql: [Warning] Using a password on the command line interface can be insecure.
+----+--------------+-------+
| id | name | price |
+----+--------------+-------+
| 1 | 珍珠奶茶 | 65 |
| 2 | 鳳梨酥 | 45 |
| 3 | 牛肉麵 | 180 |
+----+--------------+-------+
$ docker exec mysql mysql -uroot -psecret -t -e "SELECT VERSION(), @@character_set_server, @@collation_server;"
mysql: [Warning] Using a password on the command line interface can be insecure.
+-----------+------------------------+--------------------+
| VERSION() | @@character_set_server | @@collation_server |
+-----------+------------------------+--------------------+
| 8.4.11 | utf8mb4 | utf8mb4_0900_ai_ci |
+-----------+------------------------+--------------------+
-tprints a table (in non-interactive mode the default is tab-separated output).- The Chinese data is stored and read correctly: the server character set is
utf8mb4and the collation isutf8mb4_0900_ai_ci. These are MySQL 8 defaults, so no extra setup is needed. The table borders on the Chinese rows don't line up because of how themysqlclient counts the width of CJK characters; the data itself is fine. - The
[Warning] Using a password on the command line interface can be insecure.that appears every time warns that a password in a command can be seen by others in the process list. You can ignore it in demos.
From the host: localhost vs 127.0.0.1
On the host, connect any MySQL client (MySQL Workbench, DBeaver, the mysql command) to 127.0.0.1:3306. Write 127.0.0.1, not localhost. Here a container with --network host stands in for a mysql client on the host:
$ docker run --rm --network host mysql:8.4 mysql -h localhost -uapp -papppass -e "SELECT 1"
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
$ docker run --rm --network host mysql:8.4 mysql -h 127.0.0.1 -uapp -papppass -e "SELECT USER(), CURRENT_USER()"
mysql: [Warning] Using a password on the command line interface can be insecure.
USER() CURRENT_USER()
app@172.18.0.1 app@%
-h localhost: fails withCan't connect to local MySQL server through socket. When the MySQL client seeslocalhost, it connects through a Unix socket file instead of TCP, but there is no such socket file on the host (it is inside the container).-h 127.0.0.1: works, using TCP through the port published with-p.USER()showsapp@172.18.0.1: from MySQL's point of view, the connection comes from the Docker network's gateway IP, not from127.0.0.1.app@%inCURRENT_USER()means this account may connect from any host.
From other containers
Put MySQL and your application on the same user-defined network and they can connect by container name (the default bridge network has no name resolution; see Networking and container communication):
$ docker network create appnet
595dfaad48e7460c5996b90a62402e50e31fb790a1f04b3cdc67ac12be482db6
$ docker network connect appnet mysql
$ docker run --rm --network appnet mysql:8.4 mysql -h mysql -uapp -papppass app -e "SELECT COUNT(*) FROM products"
mysql: [Warning] Using a password on the command line interface can be insecure.
COUNT(*)
3
The application's connection string:
mysql://app:apppass@mysql:3306/app
The host name is the container name mysql (in Compose, the service name), and the port is the container's own 3306. Containers talking to each other don't need -p.
Managing it with Compose
For real projects, use Compose. Add compose.yaml, .env and a custom config file my.cnf to mysql-demo:
mysql-demo/
├── compose.yaml
├── .env
├── my.cnf
└── initdb/
├── 01-schema.sql
└── 02-seed.sql
.env:
MYSQL_ROOT_PASSWORD=change-this-root-password
MYSQL_PASSWORD=change-this-app-password
my.cnf:
[mysqld]
max_connections = 300
innodb_buffer_pool_size = 256M
compose.yaml:
services:
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: app
MYSQL_USER: app
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
TZ: Asia/Taipei
ports:
- "127.0.0.1:3306:3306"
volumes:
- db_data:/var/lib/mysql
- ./initdb:/docker-entrypoint-initdb.d:ro
- ./my.cnf:/etc/mysql/conf.d/custom.cnf:ro
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p\"$$MYSQL_ROOT_PASSWORD\" --silent"]
interval: 10s
retries: 10
start_period: 30s
restart: unless-stopped
volumes:
db_data:
| Setting | Description |
|---|---|
TZ: Asia/Taipei | Sets the container's time zone; MySQL's NOW() uses it |
./my.cnf:/etc/mysql/conf.d/custom.cnf | MySQL reads the .cnf files in /etc/mysql/conf.d/ |
$$MYSQL_ROOT_PASSWORD | $$ stops Compose from substituting the variable itself, so the shell inside the container reads the container's environment variable |
-h 127.0.0.1 | Important, see below |
$ docker compose up -d --wait
[+] up 3/3
✔ Network mysql-demo_default Created 0.0s
✔ Volume mysql-demo_db_data Created 0.0s
✔ Container mysql-demo-db-1 Healthy 10.9s
$ docker compose exec db sh -c 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SELECT 1"'
mysql: [Warning] Using a password on the command line interface can be insecure.
1
1
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
mysql-demo-db-1 mysql:8.4 "docker-entrypoint.s…" db 12 seconds ago Up 11 seconds (healthy) 127.0.0.1:3306->3306/tcp, 33060/tcp
$ docker compose exec db sh -c 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -t -e "SELECT @@max_connections, @@innodb_buffer_pool_size DIV 1048576 AS buffer_pool_mb, @@system_time_zone, NOW();"'
mysql: [Warning] Using a password on the command line interface can be insecure.
+-------------------+----------------+--------------------+---------------------+
| @@max_connections | buffer_pool_mb | @@system_time_zone | NOW() |
+-------------------+----------------+--------------------+---------------------+
| 300 | 256 | CST | 2026-10-13 08:49:41 |
+-------------------+----------------+--------------------+---------------------+
$ docker compose exec db sh -c 'mysql -uapp -p"$MYSQL_PASSWORD" app -t -e "SELECT * FROM products"'
mysql: [Warning] Using a password on the command line interface can be insecure.
+----+--------------+-------+
| id | name | price |
+----+--------------+-------+
| 1 | 珍珠奶茶 | 65 |
| 2 | 鳳梨酥 | 45 |
| 3 | 牛肉麵 | 180 |
+----+--------------+-------+
--waitreturns once the service is healthy.Healthytook about 11 seconds, matching the initialization time we saw earlier.max_connectionsis 300 andbuffer_pool_mbis 256, somy.cnftook effect.@@system_time_zoneisCST(the Asia/Taipei abbreviation, UTC+8), andNOW()shows Taipei time.- The init SQL ran as well:
productshas three rows.
The health check trap: localhost reports healthy too early
A MySQL health check you often see online is mysqladmin ping -h localhost. Here is a test with the 127.0.0.1 above changed to localhost:
$ docker compose up -d --wait
[+] up 3/3
✔ Network mysql-demo_default Created 0.0s
✔ Volume mysql-demo_db_data Created 0.0s
✔ Container mysql-demo-db-1 Healthy 5.8s
$ docker compose exec db sh -c 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SELECT 1"'
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
$ docker compose logs db 2>&1 | grep -E "Temporary server|init process done|port: 3306"
db-1 | 2026-10-13 08:49:02+08:00 [Note] [Entrypoint]: Temporary server started.
$ docker compose exec db sh -c 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SELECT 1"'
mysql: [Warning] Using a password on the command line interface can be insecure.
1
1
- It reports
Healthyafter only 5.8 seconds, but connecting as root right afterwards fails, and the log shows the server is still atTemporary server started. - Trying again 10 seconds later works.
The reason: the temporary server during initialization only accepts socket connections, and -h localhost happens to use the socket, so the ping gets an answer. And mysqladmin ping succeeds as soon as the server responds, even if the account can't log in yet. Compose then thinks the database is ready and starts the services that depend on it (depends_on with condition: service_healthy), so the application's first connection may fail.
With -h 127.0.0.1 the check uses TCP. The temporary server accepts no network connections (port: 0), so the check only reports healthy once the real server is running.
Backup and restore
Export SQL with mysqldump, deliberately drop the table, then restore from the backup:
$ docker exec mysql sh -c 'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction app' > app.sql
mysqldump: [Warning] Using a password on the command line interface can be insecure.
$ ls -lh app.sql && grep -c "INSERT INTO" app.sql
-rw-r--r-- 1 root root 2.0K Oct 13 00:47 app.sql
1
$ docker exec mysql mysql -uroot -psecret -e "DROP TABLE app.products"
mysql: [Warning] Using a password on the command line interface can be insecure.
$ docker exec mysql mysql -uapp -papppass app -e "SELECT COUNT(*) FROM products"
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 1146 (42S02) at line 1: Table 'app.products' doesn't exist
$ docker exec -i mysql sh -c 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" app' < app.sql
mysql: [Warning] Using a password on the command line interface can be insecure.
$ docker exec mysql mysql -uapp -papppass app -t -e "SELECT * FROM products"
mysql: [Warning] Using a password on the command line interface can be insecure.
+----+--------------+-------+
| id | name | price |
+----+--------------+-------+
| 1 | 珍珠奶茶 | 65 |
| 2 | 鳳梨酥 | 45 |
| 3 | 牛肉麵 | 180 |
+----+--------------+-------+
- The backup command is wrapped in
sh -c '...', so$MYSQL_ROOT_PASSWORDis expanded inside the container and the password never appears in the host's command history. The outer single quotes stop the host's shell from substituting the variable first. --single-transactionexports InnoDB tables from a consistent snapshot without locking them during the backup. Always use it in production.- The backup file
app.sqlis 2.0K and contains 1INSERT INTOstatement (all three rows are in one INSERT). - After
DROP TABLE, the query returnsTable 'app.products' doesn't exist. - The restore uses
docker exec -ito passapp.sqlfrom the host tomysqlin the container through standard input, and all the data comes back.
For restores use -i only, not -it. When standard input is a file instead of a terminal, -t fails right away:
$ docker exec -it mysql sh -c "echo hi" < /dev/null
cannot attach stdin to a TTY-enabled container because stdin is not a terminal
Other common backup commands:
# Back up all databases (including users and privileges)
docker exec mysql sh -c 'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --all-databases --single-transaction' > all.sql
# Back up and compress
docker exec mysql sh -c 'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction app' | gzip > app.sql.gz
# Restore a compressed backup
gunzip < app.sql.gz | docker exec -i mysql sh -c 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" app'
Backup and restore for Windows users
PowerShell doesn't support <, and > in Windows PowerShell 5.1 saves files as UTF-16, which breaks the import. Instead, create the file inside the container and copy it with docker cp. This works on every system:
# Backup: export to /tmp/app.sql inside the container, then copy it out
docker exec mysql sh -c 'mysqldump -uroot -p$MYSQL_ROOT_PASSWORD --single-transaction --result-file=/tmp/app.sql app'
docker cp mysql:/tmp/app.sql app.sql
# Restore: copy the file in, then import it inside the container
docker cp app.sql mysql:/tmp/restore.sql
docker exec mysql sh -c 'mysql -uroot -p$MYSQL_ROOT_PASSWORD app < /tmp/restore.sql'
# Backup: export to /tmp/app.sql inside the container, then copy it out
docker exec mysql sh -c 'mysqldump -uroot -p$MYSQL_ROOT_PASSWORD --single-transaction --result-file=/tmp/app.sql app'
docker cp mysql:/tmp/app.sql app.sql
# Restore: copy the file in, then import it inside the container
docker cp app.sql mysql:/tmp/restore.sql
docker exec mysql sh -c 'mysql -uroot -p$MYSQL_ROOT_PASSWORD app < /tmp/restore.sql'
REM Backup: export to /tmp/app.sql inside the container, then copy it out
docker exec mysql sh -c "mysqldump -uroot -p$MYSQL_ROOT_PASSWORD --single-transaction --result-file=/tmp/app.sql app"
docker cp mysql:/tmp/app.sql app.sql
REM Restore: copy the file in, then import it inside the container
docker cp app.sql mysql:/tmp/restore.sql
docker exec mysql sh -c "mysql -uroot -p$MYSQL_ROOT_PASSWORD app < /tmp/restore.sql"
--result-filemakes mysqldump write the file directly, without terminal redirection.- The
<is inside thesh -cstring, so the shell inside the container handles it, and PowerShell can run the command too. -p$MYSQL_ROOT_PASSWORDhas no quotes, avoiding PowerShell 5.1's problems with embedded quotes (don't use spaces in the password).
These commands were tested on Docker 29, and all 3 rows were restored:
$ docker exec mysql sh -c 'mysql -uroot -p$MYSQL_ROOT_PASSWORD app -e "CREATE TABLE t (id INT); INSERT INTO t VALUES (1),(2),(3);"'
mysql: [Warning] Using a password on the command line interface can be insecure.
$ docker exec mysql sh -c 'mysqldump -uroot -p$MYSQL_ROOT_PASSWORD --single-transaction --result-file=/tmp/app.sql app'
mysqldump: [Warning] Using a password on the command line interface can be insecure.
$ docker cp mysql:/tmp/app.sql app.sql
$ ls -l app.sql
-rw-rw-rw- 1 root root 1788 Oct 14 09:48 app.sql
$ docker exec mysql sh -c 'mysql -uroot -p$MYSQL_ROOT_PASSWORD app -e "DROP TABLE t"'
mysql: [Warning] Using a password on the command line interface can be insecure.
$ docker cp app.sql mysql:/tmp/restore.sql
$ docker exec mysql sh -c 'mysql -uroot -p$MYSQL_ROOT_PASSWORD app < /tmp/restore.sql'
mysql: [Warning] Using a password on the command line interface can be insecure.
$ docker exec mysql sh -c 'mysql -uroot -p$MYSQL_ROOT_PASSWORD app -e "SELECT COUNT(*) FROM t"'
mysql: [Warning] Using a password on the command line interface can be insecure.
COUNT(*)
3
Tested: changing the password variable has no effect
MYSQL_ROOT_PASSWORD and the other variables are only used on the first initialization (when the volume is empty). Remove the container, keep the volume, and create the container again with a different password:
$ docker rm -f mysql
mysql
$ docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=different -v mysql_data:/var/lib/mysql mysql:8.4
2a0f52d379b574033ae1571ab6873f37e8ab79ccd3830f6feac629696b74ae42
$ docker logs mysql 2>&1 | grep -c "Initializing database"
0
$ docker exec mysql mysql -uroot -pdifferent -e "SELECT 1"
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
$ docker exec mysql mysql -uroot -psecret -e "SELECT 1"
mysql: [Warning] Using a password on the command line interface can be insecure.
1
1
Initializing databaseappears 0 times in the log: the volume already has data, so the startup script skipped initialization.- The new password
differentcan't log in; the old passwordsecretstill works.
In other words, once data exists, changing MYSQL_ROOT_PASSWORD does nothing. To change the password, log in with the old one and run SQL:
ALTER USER 'root'@'%' IDENTIFIED BY 'new-password';
ALTER USER 'root'@'localhost' IDENTIFIED BY 'new-password';
Or, if you don't need the data, delete the volume and initialize again (docker compose down -v, all data is lost).
Troubleshooting
Access denied for user 'root'@'...'
$ docker exec mysql mysql -uroot -pwrong -e "SELECT 1"
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
- Wrong password, or the volume was initialized with an old password (see the test above).
- root has two accounts by default,
root@%androot@localhost, so it can connect from any host. For users you create, make sure the host is%; otherwise they can only connect from specific hosts.
Can't connect to local MySQL server through socket
The client treats localhost as a socket connection; use 127.0.0.1 (see the test above).
Exits right after starting
Check the log: docker logs mysql. Common causes:
MYSQL_ROOT_PASSWORDis not set (and neither isMYSQL_ALLOW_EMPTY_PASSWORDorMYSQL_RANDOM_ROOT_PASSWORD).- The volume contains data initialized by another version (for example 5.7 or MariaDB) that is incompatible with the current one.
- The host is out of memory.
Upgrading from 5.7 / 8.0
Don't mount the old volume directly into the new image. Export with mysqldump first, import into an empty database on the new version, and verify in a test environment first.
Recap
- Put init SQL in
/docker-entrypoint-initdb.d/; it only runs on the first initialization. - Connect from the host with
127.0.0.1;localhostswitches to the socket. - Write the health check as
mysqladmin ping -h 127.0.0.1. - Back up with
mysqldump --single-transaction, and actually test the restore.
Exercises
Exercise 1: List all databases in the MySQL container with one command.
Answer
docker exec mysql sh -c 'mysql -uroot -p$MYSQL_ROOT_PASSWORD -e "SHOW DATABASES"'
Exercise 2: The Compose MySQL service sets ports: "127.0.0.1:3306:3306". When an application in the same Compose project connects, what host name and port does it use?
Answer
The host name is the service name db, and the port is the container's own 3306. ports only affects connections from the host; containers don't need it to reach each other.
FAQ
Non-ASCII text shows up garbled?
MySQL 8 already defaults to utf8mb4. Garbled text usually comes from the client connection's character set or the encoding of the imported file. Backups saved with > in Windows PowerShell 5.1 are UTF-16; use the docker cp approach from this chapter.
Should I use MySQL or MariaDB?
They are highly compatible, and common apps such as WordPress support both. MariaDB's environment variables start with MARIADB_ instead.
I changed MYSQL_ROOT_PASSWORD but it has no effect?
The password variable is only used while the volume is empty. After that, change the password with SQL. See the test in this chapter.
Updated October 14, 2026