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

本系列為 18 小時 Docker 基礎教學講義,適合初學者,課堂指令統一使用 Linux/macOS shell 風格。 本篇為第 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 就是用來解決這個問題的——把所有容器的設定寫在一個 compose.yaml 檔案裡,一個指令就能全部啟動。

檔名提醒:新版 Docker Compose 常見檔名是 compose.yamlcompose.yml。舊檔名 docker-compose.yml 仍然可以用,但本課程從這裡開始統一使用 compose.yaml。新版 Compose V2 也不需要再寫舊式的頂層 version: "3.8"

什麼是 YAML?

compose.yaml 使用 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 等編輯器,它會幫你自動對齊。

示範:把剛才的 MySQL 改寫成 Docker Compose

前面我們已經用 docker run 啟動過 MySQL。現在不要換題目,先把同一個 MySQL 改寫成 compose.yaml,學生才會知道每一行是從哪裡來的。

先建立一個新的練習資料夾:

mkdir -p ~/docker-lab/mysql-compose
cd ~/docker-lab/mysql-compose

如果前面還有同名容器,先清掉,避免名稱或 port 衝突:

docker rm -f my-mysql

~/docker-lab/mysql-compose/ 目錄下建立 compose.yaml

services:
  db:
    image: mysql:8.0
    container_name: my-mysql
    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 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

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

compose.yamldocker run 參數說明
image:映像檔名稱要用哪個映像檔
container_name:--name容器名稱
restart:--restart重啟策略
ports:-p端口映射
volumes:-v掛載目錄
environment:-e環境變數

關於 container_name:這裡保留 container_name 是為了讓學生對照前面的 --name my-mysql。實務專案通常可以省略,讓 Compose 自己用 project name 和 service name 產生容器名稱,避免不同專案互相撞名。

restart 重啟策略:

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

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

執行這個 MySQL Compose

# 進入專案目錄
cd ~/docker-lab/mysql-compose

# 先檢查 Compose 檔案是否能被正確解析
docker compose config

# 啟動服務(背景執行)
docker compose up -d

# 查看狀態
docker compose ps

# 查看 log
docker compose logs

# 即時追蹤 log
docker compose logs -f

# 確認 MySQL 可以登入
docker compose exec db mysql -u root -p

登入後輸入 my-secret-pw,再做一次簡單確認:

SHOW DATABASES;
USE testdb;
SELECT DATABASE();
EXIT;

停止與清理:

# 停止並刪除 Compose 建立的容器與網路
docker compose down

# 連 Volume 也刪掉,MySQL 資料會一起消失
docker compose down -v

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

Compose project name 是什麼?

當你在某個資料夾執行 docker compose up,Compose 會把「目前資料夾名稱」當成預設的 project name。同一個 project 裡的 container、network、volume 會被視為同一組環境。

例如你在 ~/docker-lab/mysql-compose 執行:

docker compose up -d

Compose 通常會建立類似這樣的資源:

mysql-compose-db-1
mysql-compose_default
mysql-compose_mysql-data

所以同一份 compose.yaml 放在不同資料夾執行,會變成不同 project,不容易互相干擾。如果想明確指定 project name,可以用:

docker compose -p mysql-demo up -d
docker compose -p mysql-demo down

初學階段先記住一句話:service name 是容器彼此連線用的名字,project name 是 Compose 管理整組環境用的名字。

Docker Compose 的好處

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

教學順序提醒:這一堂先把 Compose 的「自動網路」當成會用的功能就好。也就是說,同一份 compose.yaml 裡的服務可以用服務名稱互相連線,例如 webappappdb。至於 Docker Network 是什麼、怎麼手動建立 network、--network 怎麼用,第四堂會再正式拆解。

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

ports、expose 與 depends_on 先講清楚

接下來會開始做多容器互連,所以先把三個設定分清楚:

設定用途初學者要記住
ports把容器 port 開到主機瀏覽器或主機工具要連進來才需要
expose標示容器內服務 port只給 Compose network 裡的服務連,不開到主機
depends_on控制容器啟動順序只保證先啟動,不保證服務已經 ready

例如 web 可以用 http://app:5000 連到 Flask,不需要知道 app 容器的 IP。這是 Compose 自動網路和 service name DNS 的效果。

重要depends_on 不是健康檢查。MySQL 容器開始啟動,不代表 MySQL 已經可以連線,所以應用程式仍然要能重試連線。

練習 P2:照解答跑 Nginx + Flask

這一題不是要學生憑空寫。做法是:先照完整解答建立檔案,跑起來後再改一兩個設定確認自己懂了什麼。

這次不要只是把兩個沒有關係的容器放在同一份 Compose 裡,而是做一個真的會互相連線的結構:

Browser -> Nginx -> Flask

重點是:瀏覽器只連到 Nginx,Flask 不對主機開 port。Nginx 會透過 Compose network,用服務名稱 app 連到 Flask。

目標:

  • 啟動一個 Nginx,瀏覽器用 http://localhost:8080 開啟
  • 啟動一個 Flask,容器內使用 5000 port
  • Flask 不寫 ports,只寫 expose: "5000"
  • Nginx 設定 proxy_pass http://app:5000;
  • 兩個服務都設定 restart: unless-stopped
  • docker compose ps 確認兩個服務都是 Up

建立資料夾:

mkdir -p ~/docker-lab/nginx-flask-compose
cd ~/docker-lab/nginx-flask-compose
mkdir app
mkdir nginx

建立 app/app.py

from flask import Flask, jsonify
import socket

app = Flask(__name__)


@app.get("/")
def home():
    return jsonify(
        message="Hello from Flask behind Nginx",
        hostname=socket.gethostname(),
    )


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

建立 app/requirements.txt

flask==3.0.3

建立 nginx/default.conf

server {
    listen 80;

    location / {
        proxy_pass http://app:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

建立 compose.yaml

services:
  web:
    image: nginx:alpine
    container_name: compose-nginx
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - app

  app:
    image: python:3.11
    container_name: compose-flask
    working_dir: /app
    restart: unless-stopped
    volumes:
      - ./app:/app
    expose:
      - "5000"
    command: bash -c "pip install -r requirements.txt && python app.py"

啟動與驗證:

docker compose config
docker compose up -d
docker compose ps
docker compose logs

打開瀏覽器看 http://localhost:8080,應該會看到 Flask 回傳的 JSON。

也可以從 Nginx 容器裡直接打 Flask 的服務名稱:

docker compose exec web wget -qO- http://app:5000

這裡的 app 不是主機名稱,也不是 IP,而是 Compose network 自動提供的 service name DNS。

練習改一行:

ports"8080:80" 改成 "8081:80",重新啟動:

docker compose down
docker compose up -d

再打開 http://localhost:8081。這一步是在練習 port 映射,不是在考學生背 YAML。

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

現在來動手做!我們要用一個 compose.yaml 同時啟動 Nginx、Flask 應用和 MySQL 資料庫。

這個例子會真的用到 Compose network:

Browser -> Nginx -> Flask -> MySQL

瀏覽器從主機連到 Nginx;Nginx 在 Compose network 裡用服務名稱 app 連到 Flask;Flask 再用服務名稱 db 連到 MySQL。

步驟 1:建立專案資料夾

mkdir -p ~/docker-lab/nginx-flask-mysql-compose
cd ~/docker-lab/nginx-flask-mysql-compose
mkdir app
mkdir nginx

步驟 2:建立 Flask 應用

~/docker-lab/nginx-flask-mysql-compose/app/ 下建立 app.py

import os
import time

import pymysql
from flask import Flask, jsonify

app = Flask(__name__)


def connect_db():
    return pymysql.connect(
        host=os.environ["DB_HOST"],
        user=os.environ["DB_USER"],
        password=os.environ["DB_PASSWORD"],
        database=os.environ["DB_NAME"],
        charset="utf8mb4",
        cursorclass=pymysql.cursors.DictCursor,
    )


@app.get("/")
def index():
    last_error = None

    for _ in range(10):
        try:
            with connect_db() as conn:
                with conn.cursor() as cursor:
                    cursor.execute(
                        """
                        CREATE TABLE IF NOT EXISTS visits (
                            id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
                            created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
                        )
                        """
                    )
                    cursor.execute("INSERT INTO visits () VALUES ()")
                    conn.commit()
                    cursor.execute("SELECT COUNT(*) AS count FROM visits")
                    row = cursor.fetchone()

            return jsonify(
                message="Flask connected to MySQL through the Compose network",
                db_host=os.environ["DB_HOST"],
                visits=row["count"],
            )
        except Exception as error:
            last_error = error
            time.sleep(2)

    return jsonify(error=str(last_error)), 500


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

建立 app/requirements.txt

flask==3.0.3
pymysql==1.1.1
cryptography==42.0.8

步驟 3:建立 Nginx 設定檔

~/docker-lab/nginx-flask-mysql-compose/nginx/ 下建立 default.conf

server {
    listen 80;

    location / {
        proxy_pass http://app:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

步驟 4:建立 compose.yaml

~/docker-lab/nginx-flask-mysql-compose/ 下建立 compose.yaml

services:
  # Nginx 反向代理
  web:
    image: nginx:alpine
    container_name: demo-nginx
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - app

  # Flask 應用程式
  app:
    image: python:3.11
    container_name: demo-flask
    working_dir: /app
    restart: unless-stopped
    volumes:
      - ./app:/app
    environment:
      DB_HOST: db
      DB_NAME: demo_db
      DB_USER: demo_user
      DB_PASSWORD: demo_pass
    depends_on:
      - db
    expose:
      - "5000"
    command: bash -c "pip install -r requirements.txt && python app.py"

  # 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:

這裡最重要的是兩個 service name:

  • Nginx 用 app:5000 連 Flask
  • Flask 用 db:3306 連 MySQL

MySQL 的 "3306:3306" 是為了讓你也能從主機用 DBeaver、Workbench 或 CLI 連進去觀察資料;Flask 連 MySQL 不靠這個對外 port,而是靠 Compose network 裡的 db:3306

步驟 5:啟動所有服務

cd ~/docker-lab/nginx-flask-mysql-compose
docker compose config
docker compose up -d

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

步驟 6:驗證服務

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

# 查看 log,確認 Nginx、Flask 與 MySQL 沒有錯誤
docker compose logs

打開瀏覽器:

  • http://localhost:8080 → 看到 Flask 回傳 JSON
  • 重整幾次,visits 數字會增加,代表 Flask 成功寫入 MySQL

也可以從 Nginx 容器驗證它能用 service name 連 Flask:

docker compose exec web wget -qO- http://app:5000

步驟 7:試試看修改 Flask 程式

因為我們用了 bind mount 把本機的 app 資料夾掛載進去,所以可以直接修改 app/app.py。修改後重啟 app 服務:

docker compose restart app

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

docker compose exec db mysql -u demo_user -p

輸入密碼 demo_pass

SHOW DATABASES;
USE demo_db;
SELECT * FROM visits;
EXIT;

步驟 9:體驗一鍵關閉

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

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

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

docker compose down -v

回顧一下:剛才我們用一個 compose.yaml 同時啟動 Nginx + Flask + MySQL。瀏覽器只對外連 Nginx;Nginx 用 app 找 Flask;Flask 用 db 找 MySQL。這才是多容器 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 隔離性的威力——同一台電腦輕鬆跑多個版本,互不干擾。在開發環境中,這是非常常見的需求。


挑戰 C1:補完 Nginx + Flask + MySQL 三服務 Compose

前面老師已經帶著做完整版本,這一題改成學生自己補完 compose.yaml

請打開範例資料夾:

cd docker-course-examples/session-03-mysql-compose/c1-nginx-flask-mysql/starter

目標架構:

Browser -> Nginx -> Flask -> MySQL

要求:

  • web:使用 nginx:alpine,對外開 8084:80
  • app:使用 python:3.11,只在內部 expose 5000
  • db:使用 mysql:8.0,資料掛到 named volume
  • Nginx 用 app:5000 連 Flask
  • Flask 用 DB_HOST=db 連 MySQL

驗證:

docker compose config
docker compose up -d
docker compose ps
docker compose exec web wget -qO- http://app:5000
docker stats --no-stream
docker compose down -v

這題的重點不是再抄一次 YAML,而是確認你真的理解三段關係:主機連 Nginx、Nginx 連 Flask、Flask 連 MySQL。

挑戰 C2:自己查 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 文件 → 自己把容器跑起來」的能力。以後工作上遇到任何新的映像檔,都是用同樣的流程。

不要選本堂已經大量使用的 nginxmysqlpython / flask

可選延伸:Docker Hub 有趣映像檔

用 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 stop $(docker ps -q)

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

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

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