目錄

用 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_PASSWORDroot 密碼(必填,或改用 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.

從日誌可以看到映像檔的啟動腳本在第一次啟動(資料夾是空的)時依序:

  1. 建立資料庫檔案(Initializing database files)。
  2. 啟動一個暫時的伺服器(Starting temporary server)。注意它的 port: 0:暫時伺服器不開放網路連線,只能用 socket 連線。
  3. 建立資料庫 app、使用者 app 並授權。
  4. 依檔名順序執行 /docker-entrypoint-initdb.d/ 中的 01-schema.sql、02-seed.sql。
  5. 停止暫時伺服器,以正式設定啟動,最後一行的 port: 3306 才表示真正可以連線了。

整個過程大約 10 秒。/docker-entrypoint-initdb.d/ 支援 .sql、.sql.gz、.sh 檔,而且只在第一次初始化時執行,之後重啟容器不會再執行。

版本選擇

tag說明
mysql:8.4長期支援版(LTS),建議用於正式環境
mysql:9創新版,功能較新、支援期較短
mariadb:11MariaDB,與 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.cnfMySQL 會讀取 /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日