目錄
用 Docker 跑 MySQL:安裝、連線、備份與還原
一行指令啟動
先準備一個資料夾 mysql-demo,裡面的 initdb/ 放兩個初始化用的 SQL 檔(後面會說明):
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);
在 mysql-demo 資料夾中啟動 MySQL:
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 的寫法(點選分頁切換):
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
| 參數 | 說明 |
|---|---|
MYSQL_ROOT_PASSWORD | root 密碼(必填,或改用 MYSQL_RANDOM_ROOT_PASSWORD=yes 隨機產生) |
MYSQL_DATABASE | 第一次啟動時建立的資料庫 |
MYSQL_USER / MYSQL_PASSWORD | 建立一般使用者,並給予 MYSQL_DATABASE 的完整權限 |
-p 127.0.0.1:3306:3306 | 只開放給本機,外部連不到 |
-v mysql_data:/var/lib/mysql | 資料存在名為 mysql_data 的 Volume |
-v .../initdb:/docker-entrypoint-initdb.d:ro | 初始化時要執行的 SQL |
第一次啟動做了什麼
$ 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.
從日誌可以看到映像檔的啟動腳本在第一次啟動(資料夾是空的)時依序:
- 建立資料庫檔案(
Initializing database files)。 - 啟動一個暫時的伺服器(
Starting temporary server)。注意它的port: 0:暫時伺服器不開放網路連線,只能用 socket 連線。 - 建立資料庫
app、使用者app並授權。 - 依檔名順序執行
/docker-entrypoint-initdb.d/中的01-schema.sql、02-seed.sql。 - 停止暫時伺服器,以正式設定啟動,最後一行的
port: 3306才表示真正可以連線了。
整個過程大約 10 秒。/docker-entrypoint-initdb.d/ 支援 .sql、.sql.gz、.sh 檔,而且只在第一次初始化時執行,之後重啟容器不會再執行。
版本選擇
| tag | 說明 |
|---|---|
mysql:8.4 | 長期支援版(LTS),建議用於正式環境 |
mysql:9 | 創新版,功能較新、支援期較短 |
mariadb:11 | MariaDB,與 MySQL 高度相容的分支,環境變數改為 MARIADB_ 開頭 |
連線
從容器內
容器內就有 mysql 用戶端。互動式操作用 docker exec -it mysql mysql -uapp -p,輸入密碼後進入 mysql> 提示字元。也可以用 -e 直接執行 SQL:
$ 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 |
+-----------+------------------------+--------------------+
-t以表格格式輸出(非互動模式下預設是以 Tab 分隔)。- 中文資料正確存取,伺服器字元集是
utf8mb4、排序規則是utf8mb4_0900_ai_ci,這是 MySQL 8 的預設值,不需要另外設定。中文那幾列的框線沒有對齊,是mysql用戶端計算中文寬度的方式造成的,資料本身沒有問題。 - 每次都出現的
[Warning] Using a password on the command line interface can be insecure.是提醒密碼寫在指令中可能被其他人從程序列表看到,示範時可以忽略。
從主機:localhost 與 127.0.0.1 的差別
在主機上使用任何 MySQL 用戶端(MySQL Workbench、DBeaver、mysql 指令)連到 127.0.0.1:3306。請寫 127.0.0.1 而不是 localhost。用 --network host 的容器模擬主機上的 mysql 用戶端來實測:
$ 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:失敗,Can't connect to local MySQL server through socket。MySQL 用戶端看到localhost時會改用 Unix socket 檔案連線,而不是 TCP,但主機上並沒有那個 socket 檔案(它在容器裡)。-h 127.0.0.1:成功,走 TCP 經過-p對應的連接埠。USER()顯示app@172.18.0.1:從 MySQL 的角度,連線來自 Docker 網路的閘道 IP,而不是127.0.0.1。CURRENT_USER()的app@%表示這個帳號允許從任何主機連線。
從其他容器
把 MySQL 和應用程式放在同一個自訂網路中,就能用容器名稱連線(預設的 bridge 網路不能用名稱解析,參考 網路與容器互連):
$ 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
應用程式的連線字串寫成:
mysql://app:apppass@mysql:3306/app
主機名稱是容器名稱 mysql(Compose 中則是服務名稱),連接埠是容器內的 3306。容器之間連線不需要 -p。
用 Compose 管理
實際專案建議用 Compose 管理。在 mysql-demo 中加入 compose.yaml、.env 和自訂設定檔 my.cnf:
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:
| 設定 | 說明 |
|---|---|
TZ: Asia/Taipei | 設定容器時區,MySQL 的 NOW() 會使用這個時區 |
./my.cnf:/etc/mysql/conf.d/custom.cnf | MySQL 會讀取 /etc/mysql/conf.d/ 中的 .cnf 檔 |
$$MYSQL_ROOT_PASSWORD | $$ 讓 Compose 不要自己替換變數,留給容器內的 shell 讀取容器的環境變數 |
-h 127.0.0.1 | 重要,見下方說明 |
$ 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 |
+----+--------------+-------+
--wait等到服務變成 healthy 才返回,Healthy花了約 11 秒,與前面看到的初始化時間相符。max_connections是 300、buffer_pool_mb是 256,my.cnf生效了。@@system_time_zone是CST(中原標準時間,UTC+8),NOW()顯示的是台北時間。- 初始化 SQL 一樣有執行,
products有三筆資料。
健康檢查的陷阱:localhost 會太早回報 healthy
網路上常見的 MySQL 健康檢查寫法是 mysqladmin ping -h localhost。實測把上面的 127.0.0.1 改成 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
- 只過了 5.8 秒 就回報
Healthy,但緊接著用 root 連線卻失敗,日誌顯示這時還停在Temporary server started。 - 10 秒後再試才成功。
原因是:初始化期間的暫時伺服器只能透過 socket 連線,而 -h localhost 正好使用 socket,所以 ping 得到了回應;而且 mysqladmin ping 只要伺服器有回應就算成功,即使帳號密碼還無法登入。結果 Compose 以為資料庫已經準備好,就啟動了依賴它的服務(depends_on 的 condition: service_healthy),應用程式第一次連線就可能失敗。
改成 -h 127.0.0.1 就會走 TCP,暫時伺服器不開放網路連線(port: 0),所以要等正式伺服器啟動後才會回報 healthy。
備份與還原
用 mysqldump 匯出 SQL,然後故意刪掉資料表,再從備份還原:
$ 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 |
+----+--------------+-------+
- 備份指令用
sh -c '...'包起來,$MYSQL_ROOT_PASSWORD在容器內展開,密碼不會出現在主機的指令歷史中。外層用單引號,避免主機的 shell 先替換變數。 --single-transaction讓 InnoDB 資料表在一致的快照中匯出,備份期間不會鎖住資料表,正式環境一定要加。- 備份檔
app.sql是 2.0K,包含 1 個INSERT INTO指令(三筆資料寫在同一個 INSERT 中)。 DROP TABLE之後查詢得到Table 'app.products' doesn't exist。- 還原用
docker exec -i,把主機上的app.sql透過標準輸入傳給容器內的mysql,資料完整恢復。
還原時只能用 -i,不能用 -it。標準輸入是檔案而不是終端機時,-t 會直接出錯:
$ docker exec -it mysql sh -c "echo hi" < /dev/null
cannot attach stdin to a TTY-enabled container because stdin is not a terminal
其他常用的備份指令:
# 備份所有資料庫(含使用者與權限)
docker exec mysql sh -c 'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --all-databases --single-transaction' > all.sql
# 備份並壓縮
docker exec mysql sh -c 'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction app' | gzip > app.sql.gz
# 還原壓縮的備份
gunzip < app.sql.gz | docker exec -i mysql sh -c 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" app'
Windows 使用者的備份與還原
PowerShell 不支援 <,而 Windows PowerShell 5.1 的 > 會把檔案存成 UTF-16 編碼,匯入時出錯。改成在容器內產生檔案、再用 docker cp 複製,這個寫法在所有系統都能用:
# 備份:在容器內匯出成 /tmp/app.sql,再複製出來
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
# 還原:先複製進去,再在容器內匯入
docker cp app.sql mysql:/tmp/restore.sql
docker exec mysql sh -c 'mysql -uroot -p$MYSQL_ROOT_PASSWORD app < /tmp/restore.sql'
# 備份:在容器內匯出成 /tmp/app.sql,再複製出來
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
# 還原:先複製進去,再在容器內匯入
docker cp app.sql mysql:/tmp/restore.sql
docker exec mysql sh -c 'mysql -uroot -p$MYSQL_ROOT_PASSWORD app < /tmp/restore.sql'
REM 備份:在容器內匯出成 /tmp/app.sql,再複製出來
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 還原:先複製進去,再在容器內匯入
docker cp app.sql mysql:/tmp/restore.sql
docker exec mysql sh -c "mysql -uroot -p$MYSQL_ROOT_PASSWORD app < /tmp/restore.sql"
--result-file讓 mysqldump 直接寫檔,不經過終端機的重新導向。- 指令中的
<寫在sh -c的字串裡,由容器內的 shell 處理,所以 PowerShell 也能執行。 -p$MYSQL_ROOT_PASSWORD不加引號,避免 PowerShell 5.1 處理內嵌引號的問題(密碼中不要有空白)。
這組指令在 Docker 29 實測,3 筆資料完整還原:
$ 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
實測:改了密碼變數卻沒生效
MYSQL_ROOT_PASSWORD 等變數只在第一次初始化(Volume 是空的)時使用。刪除容器、保留 Volume,然後用不同的密碼重新建立容器:
$ 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 database出現 0 次:Volume 中已經有資料,啟動腳本跳過了初始化。 - 新密碼
different無法登入,舊密碼secret仍然可以。
也就是說,資料一旦存在,修改 MYSQL_ROOT_PASSWORD 不會有任何效果。要修改密碼,請用舊密碼登入後執行 SQL:
ALTER USER 'root'@'%' IDENTIFIED BY '新密碼';
ALTER USER 'root'@'localhost' IDENTIFIED BY '新密碼';
或者在不需要保留資料時,刪除 Volume 重新初始化(docker compose down -v,資料會全部消失)。
疑難排解
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)
- 密碼錯誤,或 Volume 是用舊密碼初始化的(見上方實測)。
- root 預設有
root@%和root@localhost兩個帳號,允許從任何主機連線;自己建立的使用者要確認它的 host 是%,否則只能從特定主機連線。
Can't connect to local MySQL server through socket
用戶端把 localhost 當成 socket 連線,改用 127.0.0.1(見前面的實測)。
啟動後馬上退出
看日誌:docker logs mysql。常見原因:
- 沒有設定
MYSQL_ROOT_PASSWORD(也沒有MYSQL_ALLOW_EMPTY_PASSWORD或MYSQL_RANDOM_ROOT_PASSWORD)。 - Volume 中有其他版本(例如 5.7 或 MariaDB)初始化的資料,與目前版本不相容。
- 主機記憶體不足。
從 5.7 / 8.0 升級
不要直接把舊的 Volume 掛到新版映像檔。建議先用 mysqldump 匯出,再匯入新版本的空資料庫,並先在測試環境驗證。
重點回顧
- 初始化 SQL 放在
/docker-entrypoint-initdb.d/,只在第一次初始化時執行。 - 主機上連線寫
127.0.0.1,localhost會改用 socket。 - 健康檢查寫
mysqladmin ping -h 127.0.0.1。 - 備份用
mysqldump --single-transaction,並實際測試還原。
動手練習
練習 1:用一個指令列出 MySQL 容器中所有的資料庫。
答案
docker exec mysql sh -c 'mysql -uroot -p$MYSQL_ROOT_PASSWORD -e "SHOW DATABASES"'
練習 2:Compose 的 MySQL 設定了 ports: "127.0.0.1:3306:3306"。同一個 Compose 專案中的應用程式要連線時,主機名稱和連接埠是什麼?
答案
主機名稱是服務名稱 db,連接埠是容器內的 3306。ports 只影響從主機連線,容器之間不需要它。
常見問題
中文變成亂碼?
MySQL 8 預設已經是 utf8mb4。亂碼通常來自用戶端連線的字元集或匯入檔案的編碼;Windows PowerShell 5.1 用 > 存的備份是 UTF-16,請用本章的 docker cp 寫法。
要用 MySQL 還是 MariaDB?
兩者高度相容。WordPress 等常見程式都支援;MariaDB 的環境變數改為 MARIADB_ 開頭。
改了 MYSQL_ROOT_PASSWORD 沒有生效?
密碼變數只在 Volume 是空的時候使用,之後要用 SQL 修改,實測見本章。
更新於 2026年10月14日