Docker 教學 第 3 堂:MySQL、Docker Compose 與多容器管理

本系列為 18 小時 Docker 基礎教學講義,適合初學者,使用 Windows 電腦。 本篇為第 3 堂課,約 3 小時。

3-1 啟動 MySQL 容器與基本設定(約 1 小時)

為什麼用 Docker 跑資料庫?

傳統安裝 MySQL:

  • 下載安裝程式
  • 設定安裝路徑
  • 設定使用者密碼
  • 設定服務
  • 如果要移除,還要清掉一堆殘留檔案

用 Docker:

  • 一行指令搞定
  • 不想要了?直接刪容器,乾乾淨淨

開發環境 vs 生產環境:資料庫該不該放在容器裡?

開發環境(你的電腦)生產環境(正式上線)
用途工程師寫程式、測試真正的使用者在用
資料測試資料,丟了沒關係真實資料,不能丟
用 Docker 跑 DB?✅ 非常適合❌ 不建議

開發環境用 Docker 跑資料庫是最佳實踐——一行指令裝好、不用的時候刪掉、還能同時跑不同版本。

生產環境不建議,因為資料庫需要高效能磁碟 I/O、資料不能丟、備份和主從複製在容器裡更複雜。正式環境通常使用雲端託管服務(如 AWS RDS、GCP Cloud SQL)。

實務上的流程:開發環境(Docker 跑 MySQL)→ 測試沒問題 → 部署到生產環境(用託管服務)。

如何看 Docker Hub 官方文件找參數

在直接教指令之前,先學一個更重要的技能——怎麼自己去 Docker Hub 查參數。因為不同的映像檔支援的環境變數和設定都不一樣,不可能全部背下來。

步驟 1:進入 Docker Hub 搜尋映像檔

前往 hub.docker.com,搜尋 mysql,點進官方映像檔頁面。

步驟 2:看 Overview 說明

往下滑,官方映像檔都會有詳細的說明文件,重點看這幾個區塊:

  • How to use this image:教你怎麼啟動容器,通常會有範例指令
  • Environment Variables:列出所有支援的環境變數(-e 參數)
  • Where to Store Data:告訴你資料存在容器的哪個路徑(-v 要掛載的路徑)

步驟 3:找到你需要的參數

以 MySQL 為例,官方文件會列出這些環境變數:

環境變數說明是否必填
MYSQL_ROOT_PASSWORDroot 密碼必填
MYSQL_DATABASE自動建立一個資料庫選填
MYSQL_USER自動建立一個使用者選填
MYSQL_PASSWORD該使用者的密碼選填
MYSQL_ALLOW_EMPTY_PASSWORD允許空密碼選填
MYSQL_RANDOM_ROOT_PASSWORD自動產生隨機 root 密碼選填

文件也會告訴你資料存放路徑是 /var/lib/mysql,所以你就知道 -v 要掛載這個路徑。

養成好習慣:每次使用新的映像檔之前,先去 Docker Hub 看一下官方文件。這比 Google 搜尋「怎麼用 Docker 跑 XXX」更準確,因為官方文件永遠是最新的。

實作:啟動 MySQL 容器

docker run -d ^
  --name my-mysql ^
  -e MYSQL_ROOT_PASSWORD=my-secret-pw ^
  -e MYSQL_DATABASE=testdb ^
  -e MYSQL_USER=testuser ^
  -e MYSQL_PASSWORD=testpass ^
  -p 3306:3306 ^
  mysql:8.0

參數解釋:

  • -d:背景執行
  • --name my-mysql:容器名稱
  • -e:設定環境變數(Environment Variable)
    • MYSQL_ROOT_PASSWORD:root 密碼(必填)
    • MYSQL_DATABASE:自動建立一個資料庫
    • MYSQL_USER / MYSQL_PASSWORD:自動建立一個使用者
  • -p 3306:3306:映射 MySQL 預設端口
  • mysql:8.0:使用 MySQL 8.0 映像檔

環境變數就像是啟動容器時傳入的「設定值」。不同的映像檔支援不同的環境變數,可以在 Docker Hub 的說明頁面找到。

確認 MySQL 已啟動:

docker ps
docker logs my-mysql

等看到 ready for connections 就代表啟動成功了。

連線到 MySQL

方法 1:從容器內部連線

docker exec -it my-mysql mysql -u root -p

輸入密碼 my-secret-pw,然後來實際操作資料庫,確認它是真的能用的:

-- 顯示所有資料庫
SHOW DATABASES;

-- 使用 testdb
USE testdb;

建立產品資料表:

CREATE TABLE Product (
    Id INT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '產品編號',
    Name VARCHAR(100) NOT NULL COMMENT '產品名稱',
    Price DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '售價',
    Stock INT NOT NULL DEFAULT 0 COMMENT '庫存數量',
    CreatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='產品資料表';

插入測試資料:

INSERT INTO Product (Name, Price, Stock) VALUES
    ('藍牙耳機', 1290.00, 50),
    ('USB-C 線', 299.00, 150),
    ('27吋螢幕', 5990.00, 20),
    ('無線滑鼠', 590.00, 80),
    ('機械鍵盤', 2490.00, 35);

查詢資料,驗證資料庫正常運作:

-- 查看所有產品
SELECT * FROM Product;

-- 查詢庫存低於 50 的產品
SELECT Name, Stock FROM Product WHERE Stock < 50;

-- 查詢總庫存價值
SELECT SUM(Price * Stock) AS '總庫存價值' FROM Product;

-- 更新價格
UPDATE Product SET Price = 1190.00 WHERE Name = '藍牙耳機';

-- 確認更新
SELECT * FROM Product WHERE Name = '藍牙耳機';

-- 刪除一筆資料
DELETE FROM Product WHERE Name = 'USB-C 線';

-- 確認刪除
SELECT * FROM Product;

-- 離開 MySQL
EXIT;

透過以上操作,你可以確認 Docker 容器裡的 MySQL 是一個完整可用的資料庫,支援 CREATE、INSERT、SELECT、UPDATE、DELETE 所有基本操作。

方法 2:從主機用工具連線

因為我們有做端口映射(-p 3306:3306),你也可以用外部工具連線:

  • 主機:localhost
  • 端口:3306
  • 帳號:root / 密碼:my-secret-pw
  • 或帳號:testuser / 密碼:testpass

推薦工具:DBeaver、MySQL Workbench、HeidiSQL

資料持久化

問題演示:

先在 MySQL 裡新增一些資料,然後:

# 刪除容器
docker rm -f my-mysql

# 重新建立一個
docker run -d --name my-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -p 3306:3306 mysql:8.0

# 進去看看
docker exec -it my-mysql mysql -u root -p
SHOW DATABASES;
-- testdb 不見了!之前的資料全部消失!

解決方案:使用 Volume

docker rm -f my-mysql

docker run -d ^
  --name my-mysql ^
  -e MYSQL_ROOT_PASSWORD=my-secret-pw ^
  -e MYSQL_DATABASE=testdb ^
  -p 3306:3306 ^
  -v mysql-data:/var/lib/mysql ^
  mysql:8.0
  • -v mysql-data:/var/lib/mysql:建立一個名為 mysql-data 的 Volume,掛載到容器內 MySQL 存放資料的目錄

Volume 管理指令:

# 查看所有 Volume
docker volume ls

# 查看 Volume 詳情
docker volume inspect mysql-data

# 刪除 Volume
docker volume rm mysql-data

現在即使刪掉容器再重建,只要掛載同一個 Volume,資料就不會消失。

練習:加入資料 → 刪除容器 → 重建容器(掛同一個 Volume) → 確認資料還在。


3-2 Docker Compose:一次管理多個容器(約 1 小時)

為什麼需要 Docker Compose?

回想一下,前面我們啟動 MySQL 的指令有多長:

docker run -d --name my-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -e MYSQL_DATABASE=testdb -e MYSQL_USER=testuser -e MYSQL_PASSWORD=testpass -p 3306:3306 -v mysql-data:/var/lib/mysql mysql:8.0

如果同時要啟動 Web + 資料庫 + 快取,每個都要打一長串指令,而且還要記得建立網路、設定連線。這很麻煩,也很容易打錯。

Docker Compose 就是用來解決這個問題的——把所有容器的設定寫在一個 docker-compose.yml 檔案裡,一個指令就能全部啟動。

什麼是 YAML?

docker-compose.yml 使用 YAML 格式。YAML 是一種簡潔的設定檔格式,用來告訴電腦「這個服務要怎麼啟動」、「資料在哪裡」、「名字是什麼」等等。

YAML 的基本規則:

  • 縮排(空格)表示層級關係,不能用 Tab
  • : 分隔 key 和 value
  • - 表示清單項目
  • # 開頭是註解
# 這是一個 YAML 範例
name: my-app          # key: value
ports:                 # 底下是清單
  - "8080:80"         # 清單項目用 - 開頭
  - "443:443"
environment:           # 底下也是 key: value
  DB_HOST: mysql
  DB_PORT: 3306

注意:YAML 對縮排非常敏感,多一個或少一個空格都會出錯。建議用 VS Code 等編輯器,它會幫你自動對齊。

docker-compose.yml 基礎語法

C:\docker-lab\flask-app\ 目錄下建立 docker-compose.yml

services:
  web:
    image: python:3.11-slim
    container_name: flask-web        # 指定容器名稱
    restart: unless-stopped          # 重啟策略
    ports:
      - "5000:5000"
    volumes:
      - .:/app
    working_dir: /app
    command: bash -c "pip install flask && python app.py"
    depends_on:
      - db

  db:
    image: mysql:8.0
    container_name: mysql-db         # 指定容器名稱
    restart: unless-stopped          # 重啟策略
    environment:
      MYSQL_ROOT_PASSWORD: my-secret-pw
      MYSQL_DATABASE: testdb
      MYSQL_USER: testuser
      MYSQL_PASSWORD: testpass
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql

volumes:
  mysql-data:

對照一下,每個設定對應哪個 docker run 參數:

docker-compose.ymldocker run 參數說明
image:映像檔名稱要用哪個映像檔
container_name:--name容器名稱
restart:--restart重啟策略
ports:-p端口映射
volumes:-v掛載目錄
environment:-e環境變數
working_dir:-w工作目錄
command:最後面的指令啟動指令
depends_on:(手動控制啟動順序)先啟動哪個服務

restart 重啟策略:

說明
no預設值,不自動重啟
always不管什麼原因停止,都自動重啟
unless-stopped自動重啟,除非你手動 stop 它
on-failure只有程式出錯(非正常結束)才重啟

開發環境推薦用 unless-stopped,這樣電腦重開機後 Docker Desktop 啟動時,容器也會自動跟著啟動。

執行 Docker Compose

# 進入專案目錄
cd C:\docker-lab\flask-app

# 啟動所有容器(背景執行)
docker compose up -d

# 查看狀態
docker compose ps

# 查看所有容器的 log
docker compose logs

# 即時追蹤 log
docker compose logs -f

# 停止並刪除所有容器
docker compose down

# 停止並刪除所有容器,連 Volume 也刪掉
docker compose down -v

注意:舊版 Docker 使用 docker-compose(有連字號),新版已整合為 docker compose(空格)。如果你的 Docker Desktop 是近期安裝的,直接用 docker compose 就對了。

Docker Compose 的好處

  1. 一個檔案管理所有容器:不用記一堆長指令
  2. 可重複使用docker-compose.yml 可以 commit 進 git,團隊成員 clone 下來直接 docker compose up 就能跑
  3. 自動建立網路:同一個 compose 檔案裡的容器會自動放在同一個網路,可以用服務名稱(如 db)互相連線
  4. 一鍵啟動/關閉:不用一個一個 start/stop

Docker Compose 適合開發環境和小型部署。大規模生產環境通常會使用 Kubernetes(K8s)來管理,但那是進階主題,不在本課程範圍內。

實作:用 Docker Compose 建立 Nginx + MySQL 環境

現在來動手做!我們要用一個 docker-compose.yml 同時啟動一個 Nginx 網站和一個 MySQL 資料庫。

步驟 1:建立專案資料夾

mkdir C:\docker-lab\compose-demo
cd C:\docker-lab\compose-demo

步驟 2:建立網頁檔案

C:\docker-lab\compose-demo\ 下建立 html 資料夾,然後建立 html\index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Docker Compose Demo</title>
    <style>
        body { font-family: Arial; max-width: 600px; margin: 50px auto; text-align: center; }
        h1 { color: #0db7ed; }
        .info { background: #f0f0f0; padding: 20px; border-radius: 10px; margin: 20px 0; }
    </style>
</head>
<body>
    <h1>Docker Compose 實作成功!</h1>
    <div class="info">
        <p>這個網頁由 Nginx 容器提供服務</p>
        <p>同時還有一個 MySQL 容器在背景運行</p>
        <p>兩個容器都是用一個 docker-compose.yml 一起啟動的</p>
    </div>
</body>
</html>

步驟 3:建立 docker-compose.yml

C:\docker-lab\compose-demo\ 下建立 docker-compose.yml

services:
  # Nginx 網頁伺服器
  web:
    image: nginx
    container_name: demo-web
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro

  # MySQL 資料庫
  db:
    image: mysql:8.0
    container_name: demo-mysql
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: root1234
      MYSQL_DATABASE: demo_db
      MYSQL_USER: demo_user
      MYSQL_PASSWORD: demo_pass
    ports:
      - "3306:3306"
    volumes:
      - db-data:/var/lib/mysql

volumes:
  db-data:

步驟 4:啟動所有服務

cd C:\docker-lab\compose-demo
docker compose up -d

你會看到 Docker 同時拉取 Nginx 和 MySQL 的映像檔,然後啟動兩個容器。

步驟 5:驗證服務

# 查看容器狀態,應該看到兩個都是 Up
docker compose ps

# 查看 log,確認都啟動成功
docker compose logs

打開瀏覽器:

  • http://localhost:8080 → 看到我們寫的網頁
  • MySQL 也在跑了,可以用工具連線(localhost:3306,帳號 demo_user,密碼 demo_pass)

步驟 6:試試看修改網頁

因為我們用了 -v 把本機的 html 資料夾掛載進去,所以直接修改 html\index.html 的內容,重新整理瀏覽器就會看到更新。不用重啟容器!

步驟 7:連線到 MySQL 確認資料庫可用

docker exec -it demo-mysql mysql -u demo_user -p

輸入密碼 demo_pass

SHOW DATABASES;
USE demo_db;
CREATE TABLE test (id INT PRIMARY KEY, message VARCHAR(100));
INSERT INTO test VALUES (1, 'Docker Compose works!');
SELECT * FROM test;
EXIT;

步驟 8:體驗一鍵關閉

# 停止並刪除所有容器
docker compose down

# 確認都清乾淨了
docker ps -a

如果要連 Volume 也刪掉(資料庫資料也不要了):

docker compose down -v

回顧一下:剛才我們用一個 docker-compose.yml 就同時搞定了 Nginx + MySQL,不用分別打兩段長長的 docker run 指令。這就是 Docker Compose 的威力。


3-3 實戰練習與清理(約 1 小時)

實務情境題:同時跑兩個版本的 MySQL

情境: 你現在主機已經有 mysql:8.0 的容器在跑舊專案。主管說有新客戶,他們公司用的是 mysql:9.0。但你的開發機因為要維護舊專案還是需要 8.0,這時候請再建一個 mysql:9.0 的容器出來。

為什麼這個情境很重要? 傳統安裝 MySQL,同一台電腦要跑兩個版本幾乎不可能——port 衝突、路徑衝突、服務名稱衝突,搞半天搞不定。但 Docker 的隔離性讓這件事變得超簡單。

方法 A:使用 docker run

# 舊專案的 MySQL 8.0(假設已經在跑了,用 3306 port)
docker run -d --name mysql8 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root123 mysql:8.0

# 新客戶的 MySQL 9.0(port 改成 3307,避免衝突)
docker run -d --name mysql9 -p 3307:3306 -e MYSQL_ROOT_PASSWORD=root123 mysql:9.0

重點:兩個容器內部都是 3306,但對外的 port 要錯開(3306 和 3307)。

方法 B:使用 docker-compose

services:
  mysql8:
    image: mysql:8.0
    container_name: mysql8
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: old_project
    ports:
      - "3306:3306"
    volumes:
      - mysql8-data:/var/lib/mysql

  mysql9:
    image: mysql:9.0
    container_name: mysql9
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: new_project
    ports:
      - "3307:3306"
    volumes:
      - mysql9-data:/var/lib/mysql

volumes:
  mysql8-data:
  mysql9-data:

驗證兩個都能用:

# 連線到 MySQL 8.0
docker exec -it mysql8 mysql -u root -p -e "SELECT VERSION();"

# 連線到 MySQL 9.0
docker exec -it mysql9 mysql -u root -p -e "SELECT VERSION();"

這就是 Docker 隔離性的威力——同一台電腦輕鬆跑多個版本,互不干擾。在開發環境中,這是非常常見的需求。


下載 DOS Game

用 Docker 玩復古遊戲

這一節是一個有趣的實作,讓大家體驗 Docker 的另一面——它不只能跑正經的伺服器程式,還能跑各種有趣的東西。

實作 1:DOS 遊戲映像檔

Docker Hub 上有人把經典的 DOS 遊戲打包成映像檔,可以在瀏覽器裡直接玩!

# 下載 DOS 遊戲映像檔
docker pull oldiy/dosgame-web-docker

# 啟動容器
docker run -d --name dos-game -p 8080:262 oldiy/dosgame-web-docker

打開瀏覽器前往 http://localhost:8080,你會看到一個懷舊的 DOS 遊戲選單,裡面有上百款經典遊戲可以玩!

同學可以自行到 Docker Hub 搜尋 dosgame-web-docker 查看這個映像檔的說明。這也是一個練習「看官方文件找參數」的好機會。

實作 2:2048 網頁版

docker run -d --name game-2048 -p 9090:80 alexwhen/docker-2048

打開瀏覽器前往 http://localhost:9090 就能玩 2048!

實作 3:命令列小遊戲

# 用 Alpine Linux 容器體驗命令列遊戲
docker run -it --name game-box alpine sh

在容器裡安裝文字遊戲:

# 安裝遊戲套件
apk add --no-cache bsd-games

# 玩貪食蛇
snake

# 或玩其他遊戲
tetris
worm

透過這些有趣的例子:

  1. 練習從 Docker Hub 找映像檔並查看說明
  2. 練習 -p 端口映射(注意每個映像檔的容器 port 不一樣)
  3. 練習 docker rundocker stopdocker rm 等基本操作
  4. 體驗 Docker 可以運行各種類型的應用,不只是正經的伺服器程式

鼓勵同學自己去 Docker Hub 探索其他有趣的映像檔!

課後練習:自己跑起來

以下兩個映像檔請同學自行練習——去 Docker Hub 查看說明,找出需要的參數,把它跑起來:

映像檔說明Docker Hub 頁面
ramuses/photopea線上版 Photoshop 替代品,跑在瀏覽器裡hub.docker.com/r/ramuses/photopea
xinsodev/drawdb資料庫 ER 圖設計工具,視覺化設計資料表關聯hub.docker.com/r/xinsodev/drawdb

練習重點:

  1. 到 Docker Hub 頁面閱讀說明
  2. 找出容器使用的 port 是什麼
  3. docker run 跑起來並在瀏覽器開啟
  4. 玩完後用 docker rm -f 清理

這個練習的目的不是學這兩個工具,而是練習「看 Docker Hub 文件 → 自己把容器跑起來」的能力。以後工作上遇到任何新的映像檔,都是用同樣的流程。

清理練習

趁這個機會練習容器和映像檔的清理:

# 停止所有執行中的容器
docker stop $(docker ps -q)

# 刪除所有已停止的容器
docker container prune

# 刪除不再使用的映像檔
docker image prune

# 核彈級清理(小心使用!)
docker system prune -a
0%