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


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

&lt;!--more--&gt;

## 3-1 啟動 MySQL 容器與基本設定（約 1 小時）

### 為什麼用 Docker 跑資料庫？

傳統安裝 MySQL：
- 下載安裝程式
- 設定安裝路徑
- 設定使用者密碼
- 設定服務
- 如果要移除，還要清掉一堆殘留檔案

用 Docker：
- 一行指令搞定
- 不想要了？直接刪容器，乾乾淨淨

&gt; **開發環境 vs 生產環境：資料庫該不該放在容器裡？**
&gt;
&gt; | | 開發環境（你的電腦） | 生產環境（正式上線） |
&gt; |---|---|---|
&gt; | 用途 | 工程師寫程式、測試 | 真正的使用者在用 |
&gt; | 資料 | 測試資料，丟了沒關係 | 真實資料，不能丟 |
&gt; | 用 Docker 跑 DB？ | ✅ 非常適合 | ❌ 不建議 |
&gt;
&gt; **開發環境用 Docker 跑資料庫是最佳實踐**——一行指令裝好、不用的時候刪掉、還能同時跑不同版本。
&gt;
&gt; **生產環境不建議**，因為資料庫需要高效能磁碟 I/O、資料不能丟、備份和主從複製在容器裡更複雜。正式環境通常使用雲端託管服務（如 AWS RDS、GCP Cloud SQL）。
&gt;
&gt; 實務上的流程：開發環境（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_PASSWORD` | root 密碼 | **必填** |
| `MYSQL_DATABASE` | 自動建立一個資料庫 | 選填 |
| `MYSQL_USER` | 自動建立一個使用者 | 選填 |
| `MYSQL_PASSWORD` | 該使用者的密碼 | 選填 |
| `MYSQL_ALLOW_EMPTY_PASSWORD` | 允許空密碼 | 選填 |
| `MYSQL_RANDOM_ROOT_PASSWORD` | 自動產生隨機 root 密碼 | 選填 |

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

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

### 實作：啟動 MySQL 容器

```powershell
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 映像檔

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

**確認 MySQL 已啟動：**
```powershell
docker ps
docker logs my-mysql
```

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

### 連線到 MySQL

**方法 1：從容器內部連線**
```powershell
docker exec -it my-mysql mysql -u root -p
```

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

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

-- 使用 testdb
USE testdb;
```

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

**插入測試資料：**
```sql
INSERT INTO Product (Name, Price, Stock) VALUES
    (&#39;藍牙耳機&#39;, 1290.00, 50),
    (&#39;USB-C 線&#39;, 299.00, 150),
    (&#39;27吋螢幕&#39;, 5990.00, 20),
    (&#39;無線滑鼠&#39;, 590.00, 80),
    (&#39;機械鍵盤&#39;, 2490.00, 35);
```

**查詢資料，驗證資料庫正常運作：**
```sql
-- 查看所有產品
SELECT * FROM Product;

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

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

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

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

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

-- 確認刪除
SELECT * FROM Product;

-- 離開 MySQL
EXIT;
```

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

**方法 2：從主機用工具連線**

因為我們有做端口映射（`-p 3306:3306`），你也可以用外部工具連線：
- 主機：`localhost`
- 端口：`3306`
- 帳號：`root` / 密碼：`my-secret-pw`
- 或帳號：`testuser` / 密碼：`testpass`

推薦工具：DBeaver、MySQL Workbench、HeidiSQL

### 資料持久化

**問題演示：**

先在 MySQL 裡新增一些資料，然後：
```powershell
# 刪除容器
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
```

```sql
SHOW DATABASES;
-- testdb 不見了！之前的資料全部消失！
```

**解決方案：使用 Volume**
```powershell
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 管理指令：**
```powershell
# 查看所有 Volume
docker volume ls

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

# 刪除 Volume
docker volume rm mysql-data
```

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

&gt; **練習**：加入資料 → 刪除容器 → 重建容器（掛同一個 Volume） → 確認資料還在。

---

## 3-2 Docker Compose：一次管理多個容器（約 1 小時）

### 為什麼需要 Docker Compose？

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

```powershell
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 &#43; 資料庫 &#43; 快取，每個都要打一長串指令，而且還要記得建立網路、設定連線。這很麻煩，也很容易打錯。

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

### 什麼是 YAML？

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

**YAML 的基本規則：**
- 用**縮排**（空格）表示層級關係，不能用 Tab
- 用 `:` 分隔 key 和 value
- 用 `-` 表示清單項目
- `#` 開頭是註解

```yaml
# 這是一個 YAML 範例
name: my-app          # key: value
ports:                 # 底下是清單
  - &#34;8080:80&#34;         # 清單項目用 - 開頭
  - &#34;443:443&#34;
environment:           # 底下也是 key: value
  DB_HOST: mysql
  DB_PORT: 3306
```

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

### docker-compose.yml 基礎語法

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

```yaml
services:
  web:
    image: python:3.11-slim
    container_name: flask-web        # 指定容器名稱
    restart: unless-stopped          # 重啟策略
    ports:
      - &#34;5000:5000&#34;
    volumes:
      - .:/app
    working_dir: /app
    command: bash -c &#34;pip install flask &amp;&amp; python app.py&#34;
    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:
      - &#34;3306:3306&#34;
    volumes:
      - mysql-data:/var/lib/mysql

volumes:
  mysql-data:
```

**對照一下，每個設定對應哪個 `docker run` 參數：**

| docker-compose.yml | docker 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` | 只有程式出錯（非正常結束）才重啟 |

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

### 執行 Docker Compose

```powershell
# 進入專案目錄
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
```

&gt; **注意**：舊版 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

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

### 實作：用 Docker Compose 建立 Nginx &#43; MySQL 環境

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

**步驟 1：建立專案資料夾**

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

**步驟 2：建立網頁檔案**

在 `C:\docker-lab\compose-demo\` 下建立 `html` 資料夾，然後建立 `html\index.html`：

```html
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;meta charset=&#34;utf-8&#34;&gt;
    &lt;title&gt;Docker Compose Demo&lt;/title&gt;
    &lt;style&gt;
        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; }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;Docker Compose 實作成功！&lt;/h1&gt;
    &lt;div class=&#34;info&#34;&gt;
        &lt;p&gt;這個網頁由 Nginx 容器提供服務&lt;/p&gt;
        &lt;p&gt;同時還有一個 MySQL 容器在背景運行&lt;/p&gt;
        &lt;p&gt;兩個容器都是用一個 docker-compose.yml 一起啟動的&lt;/p&gt;
    &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
```

**步驟 3：建立 docker-compose.yml**

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

```yaml
services:
  # Nginx 網頁伺服器
  web:
    image: nginx
    container_name: demo-web
    restart: unless-stopped
    ports:
      - &#34;8080:80&#34;
    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:
      - &#34;3306:3306&#34;
    volumes:
      - db-data:/var/lib/mysql

volumes:
  db-data:
```

**步驟 4：啟動所有服務**

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

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

**步驟 5：驗證服務**

```powershell
# 查看容器狀態，應該看到兩個都是 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 確認資料庫可用**

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

輸入密碼 `demo_pass`：

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

**步驟 8：體驗一鍵關閉**

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

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

如果要連 Volume 也刪掉（資料庫資料也不要了）：
```powershell
docker compose down -v
```

&gt; **回顧一下**：剛才我們用一個 `docker-compose.yml` 就同時搞定了 Nginx &#43; 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**

```powershell
# 舊專案的 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**

```yaml
services:
  mysql8:
    image: mysql:8.0
    container_name: mysql8
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: old_project
    ports:
      - &#34;3306:3306&#34;
    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:
      - &#34;3307:3306&#34;
    volumes:
      - mysql9-data:/var/lib/mysql

volumes:
  mysql8-data:
  mysql9-data:
```

**驗證兩個都能用：**
```powershell
# 連線到 MySQL 8.0
docker exec -it mysql8 mysql -u root -p -e &#34;SELECT VERSION();&#34;

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

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

---

### 下載 DOS Game

#### 用 Docker 玩復古遊戲

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

#### 實作 1：DOS 遊戲映像檔

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

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

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

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

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

#### 實作 2：2048 網頁版

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

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

#### 實作 3：命令列小遊戲

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

在容器裡安裝文字遊戲：
```sh
# 安裝遊戲套件
apk add --no-cache bsd-games

# 玩貪食蛇
snake

# 或玩其他遊戲
tetris
worm
```

&gt; 透過這些有趣的例子：
&gt; 1. 練習從 Docker Hub 找映像檔並查看說明
&gt; 2. 練習 `-p` 端口映射（注意每個映像檔的容器 port 不一樣）
&gt; 3. 練習 `docker run`、`docker stop`、`docker rm` 等基本操作
&gt; 4. 體驗 Docker 可以運行各種類型的應用，不只是正經的伺服器程式
&gt;
&gt; **鼓勵同學自己去 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` 清理

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

### 清理練習

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

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

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

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

# 核彈級清理（小心使用！）
docker system prune -a
```


---

> 作者: luk  
> URL: https://yoru-karu-blog-lalaluk-52581ac5e0cef170a3c8922c19182ecb6f7bd604.gitlab.io/posts/tutorial/docker/docker-session3-mysql-compose/  

