# Docker 教學 第 5 堂：CI/CD 概念與 GitHub Actions 入門


## 5-1 什麼是 CI/CD？（約 30 分鐘）

### 先講一個故事

想像你在一間餐廳工作：

- **傳統做法**：廚師做完一道菜 → 自己端出去 → 自己收盤子 → 回來再做下一道。每件事都要手動做。
- **自動化做法**：廚師做完菜放到出餐口 → 傳送帶自動送到客人桌上 → 髒盤子自動回收。廚師只需要專心做菜。

CI/CD 就是軟體開發的「傳送帶」——你只需要專心寫程式碼，剩下的測試、打包、部署全部自動完成。

### CI/CD 的定義

**CI（Continuous Integration，持續整合）：**
- 開發者把程式碼推到 Git 之後，**自動**執行測試
- 確保每次改動都不會弄壞現有的功能
- 「我 push 了程式碼 → 系統自動幫我跑測試 → 告訴我有沒有通過」

**CD（Continuous Deployment / Delivery，持續部署/交付）：**
- 測試通過之後，**自動**把程式打包、部署到伺服器
- 不需要手動 SSH 進伺服器去更新程式
- 「測試通過 → 自動 build Docker Image → 自動部署到伺服器」

**完整流程：**
```
開發者 push 程式碼
    ↓
CI：自動跑測試
    ↓ （測試通過）
CD：自動 build Docker Image
    ↓
CD：自動 push 到 Docker Hub
    ↓
CD：自動部署到伺服器
    ↓
使用者看到最新版本
```

### 沒有 CI/CD 的痛苦

| 手動流程 | CI/CD 自動化 |
|---------|------------|
| 自己跑測試，常常忘記跑 | 每次 push 自動跑，不會漏 |
| 自己打 `docker build`、`docker push` | 自動 build 和 push |
| SSH 進伺服器手動更新 | 自動部署 |
| 「我本地測過了，怎麼上線就壞了？」 | 每次都在統一環境測試 |
| 部署一次要 30 分鐘 | 部署一次只要等幾分鐘 |

### 常見的 CI/CD 工具

| 工具 | 說明 |
|------|------|
| **GitHub Actions** | GitHub 內建，目前最主流，本課程使用 |
| **GitLab CI/CD** | GitLab 內建，GitLab 用戶首選 |
| **Jenkins** | 老牌工具，需要自己架伺服器，新專案較少使用 |
| **CircleCI** | 第三方服務 |

&gt; 本課程使用 **GitHub Actions**，因為大部分人都有 GitHub 帳號、免費額度最多（每月 2000 分鐘）、學習資源最豐富。

---

## 5-2 GitHub Actions 基礎（約 30 分鐘）

### 前置準備

1. 確認你有 **GitHub 帳號**（沒有的話去 github.com 註冊）
2. 安裝 **Git**（Windows 用戶下載 Git for Windows）
3. 確認 Git 設定好了：
```powershell
git --version
git config --global user.name &#34;你的名字&#34;
git config --global user.email &#34;你的email&#34;
```

### 核心概念

GitHub Actions 的架構很簡單，只有四個概念：

```
Repository（你的 Git 專案）
  └── .github/workflows/     ← 放 workflow 檔案的地方
        └── ci.yml            ← 一個 workflow 檔案
              ├── Event（觸發條件）：什麼時候要跑？例如 push 時
              ├── Job（工作）：要做什麼大任務？
              │     ├── Step 1（步驟）：具體的一個動作
              │     ├── Step 2
              │     └── Step 3
              └── Runner（執行環境）：在哪裡跑？例如 Ubuntu
```

**用白話解釋：**
- **Workflow**：一份「自動化腳本」，寫在 YAML 檔案裡
- **Event**：什麼事件觸發這個腳本（例如 push、發 PR）
- **Job**：腳本裡的一個大任務
- **Step**：大任務裡的一個步驟
- **Runner**：GitHub 提供的免費虛擬機，幫你跑這些步驟

### 第一個 Workflow：Hello GitHub Actions

**步驟 1：在 GitHub 上建立新的 Repository**

到 GitHub 點「New Repository」：
- Repository name：`docker-cicd-demo`
- 勾選「Add a README file」
- 點「Create repository」

**步驟 2：Clone 到本機**

```powershell
cd C:\docker-lab
git clone https://github.com/你的帳號/docker-cicd-demo.git
cd docker-cicd-demo
```

**步驟 3：建立 workflow 檔案**

```powershell
mkdir -p .github/workflows
```

在 `.github/workflows/` 下建立 `hello.yml`：

```yaml
# Workflow 的名稱（會顯示在 GitHub 的 Actions 頁面）
name: Hello GitHub Actions

# Event：什麼時候觸發？
on:
  push:
    branches: [ main ]    # 當 push 到 main 分支時觸發

# Jobs：要做什麼？
jobs:
  say-hello:                # Job 的 ID（自己取名）
    runs-on: ubuntu-latest  # Runner：在 Ubuntu 虛擬機上跑

    steps:                  # 步驟
      - name: 打招呼
        run: echo &#34;Hello GitHub Actions!&#34;

      - name: 顯示目前時間
        run: date

      - name: 顯示系統資訊
        run: |
          echo &#34;作業系統：&#34;
          uname -a
          echo &#34;目前目錄：&#34;
          pwd
```

**步驟 4：Push 到 GitHub**

```powershell
git add .
git commit -m &#34;add first workflow&#34;
git push
```

**步驟 5：到 GitHub 看結果**

1. 打開你的 Repository 頁面
2. 點上方的 **「Actions」** 分頁
3. 你會看到一個正在跑（或已完成）的 workflow
4. 點進去可以看到每個 Step 的輸出

&gt; 恭喜！你剛才讓 GitHub 的伺服器自動幫你跑了一段程式。這就是 CI/CD 的起點。

### Workflow 語法詳解

```yaml
name: CI Pipeline          # Workflow 名稱

on:                         # 觸發條件
  push:                     # push 時觸發
    branches: [ main ]
  pull_request:             # 發 PR 時也觸發
    branches: [ main ]

jobs:
  build:                    # Job ID
    runs-on: ubuntu-latest  # 執行環境

    steps:
      # 使用別人寫好的 Action（用 uses）
      - name: 拉取程式碼
        uses: actions/checkout@v4

      # 自己寫指令（用 run）
      - name: 安裝依賴
        run: pip install -r requirements.txt

      # 多行指令用 |
      - name: 執行多個指令
        run: |
          echo &#34;第一行&#34;
          echo &#34;第二行&#34;
```

**Step 的兩種寫法：**

| | `uses` | `run` |
|---|---|---|
| 用途 | 使用別人寫好的 Action | 自己寫 shell 指令 |
| 範例 | `uses: actions/checkout@v4` | `run: echo &#34;hello&#34;` |
| 類比 | 用 npm 套件 | 自己寫程式碼 |

&gt; `actions/checkout@v4` 是最常用的 Action，功能是把你的程式碼拉到 Runner 上。幾乎每個 workflow 都會用到。

---

## 5-3 實作：自動跑 Python 測試（約 1 小時）

### 建立一個有測試的 Python 專案

**步驟 1：建立專案檔案**

在 `docker-cicd-demo` 目錄下建立 `app.py`：

```python
def add(a, b):
    return a &#43; b

def subtract(a, b):
    return a - b

def multiply(a, b):
    return a * b

def is_even(n):
    return n % 2 == 0
```

建立 `test_app.py`：

```python
from app import add, subtract, multiply, is_even

def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0
    assert add(0, 0) == 0

def test_subtract():
    assert subtract(5, 3) == 2
    assert subtract(1, 1) == 0

def test_multiply():
    assert multiply(3, 4) == 12
    assert multiply(0, 5) == 0

def test_is_even():
    assert is_even(2) == True
    assert is_even(3) == False
    assert is_even(0) == True
```

建立 `requirements.txt`：

```
pytest
```

**步驟 2：本機先跑一次測試確認**

```powershell
pip install pytest
pytest test_app.py -v
```

應該會看到所有測試都 PASSED。

**步驟 3：建立 CI workflow**

建立 `.github/workflows/ci.yml`：

```yaml
name: Python CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: 拉取程式碼
        uses: actions/checkout@v4

      - name: 安裝 Python
        uses: actions/setup-python@v5
        with:
          python-version: &#39;3.11&#39;

      - name: 安裝依賴
        run: pip install -r requirements.txt

      - name: 執行測試
        run: pytest test_app.py -v
```

**步驟 4：Push 並觀察**

```powershell
git add .
git commit -m &#34;add Python CI with tests&#34;
git push
```

到 GitHub 的 Actions 頁面看，你會看到 CI 自動跑起來了，測試結果會顯示在上面。

### 體驗 CI 擋住壞掉的程式碼

**步驟 5：故意寫一個有 bug 的改動**

修改 `app.py`，把 `add` 改壞：

```python
def add(a, b):
    return a - b    # 故意寫錯！
```

```powershell
git add .
git commit -m &#34;break add function&#34;
git push
```

**步驟 6：看 CI 結果**

到 Actions 頁面，你會看到這次的 workflow **紅色打叉（失敗）**！

點進去看 log，會清楚顯示哪個測試失敗了：
```
FAILED test_app.py::test_add - assert -1 == 5
```

&gt; 這就是 CI 的價值——壞掉的程式碼會被立刻抓到，不會偷偷混進去。

**步驟 7：修好然後再推**

把 `app.py` 改回正確的，推上去，CI 就會變回綠色。

---

## 5-4 實作：自動 Build Docker Image（約 1 小時）

### 把 Docker 加入 CI 流程

現在我們要做更進階的事——每次 push 程式碼，自動 build Docker Image 並 push 到 Docker Hub。

**步驟 1：建立 Dockerfile**

在 `docker-cicd-demo` 目錄下建立 `Dockerfile`：

```dockerfile
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD [&#34;python&#34;, &#34;-m&#34;, &#34;pytest&#34;, &#34;test_app.py&#34;, &#34;-v&#34;]
```

**步驟 2：在 GitHub 設定 Docker Hub 密碼**

因為 workflow 要推映像檔到 Docker Hub，需要帳號密碼。但密碼不能寫在程式碼裡！GitHub 提供了 **Secrets** 功能來安全存放。

1. 到你的 Repository → **Settings** → **Secrets and variables** → **Actions**
2. 點 **New repository secret**
3. 新增兩個 secret：
   - Name: `DOCKERHUB_USERNAME`，Value: 你的 Docker Hub 帳號
   - Name: `DOCKERHUB_TOKEN`，Value: 你的 Docker Hub Access Token

&gt; **Docker Hub Access Token 怎麼拿？**
&gt; Docker Hub → 右上角帳號 → Account Settings → Security → New Access Token

**步驟 3：建立 Docker build workflow**

建立 `.github/workflows/docker.yml`：

```yaml
name: Docker Build &amp; Push

on:
  push:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: 拉取程式碼
        uses: actions/checkout@v4

      - name: 安裝 Python
        uses: actions/setup-python@v5
        with:
          python-version: &#39;3.11&#39;

      - name: 安裝依賴
        run: pip install -r requirements.txt

      - name: 執行測試
        run: pytest test_app.py -v

  docker:
    runs-on: ubuntu-latest
    needs: test              # 等 test job 通過才跑
    steps:
      - name: 拉取程式碼
        uses: actions/checkout@v4

      - name: 登入 Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build 並 Push Docker Image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ${{ secrets.DOCKERHUB_USERNAME }}/docker-cicd-demo:latest
            ${{ secrets.DOCKERHUB_USERNAME }}/docker-cicd-demo:${{ github.sha }}
```

**步驟 4：Push 並觀察**

```powershell
git add .
git commit -m &#34;add Docker build CI/CD&#34;
git push
```

到 Actions 頁面觀察，你會看到：
1. 先跑 `test` job（測試）
2. 測試通過後，自動跑 `docker` job（build &#43; push）
3. 完成後，到 Docker Hub 看你的映像檔已經自動上傳了！

**流程圖：**
```
git push → 自動跑測試 → 測試通過 → 自動 build Image → 自動 push 到 Docker Hub
                         ↓ 測試失敗
                    停止，不會 build
```

&gt; **`needs: test`** 是關鍵——它確保測試沒通過就不會 build 和 push，避免壞掉的程式碼被打包成映像檔。

### 理解 Workflow 裡的變數

```yaml
${{ secrets.DOCKERHUB_USERNAME }}   # 從 Secrets 讀取（安全，不會顯示在 log 裡）
${{ github.sha }}                   # 這次 commit 的 hash（用來當版本號）
```

用 commit hash 當 tag 的好處是每次 push 都有獨立的版本號，方便回滾。

---

## 課堂練習

### 練習 1：修改 workflow 觸發條件

修改 `hello.yml`，讓它在建立 Pull Request 時也會觸發。

### 練習 2：新增測試函數

在 `app.py` 新增一個 `divide(a, b)` 函數，在 `test_app.py` 新增對應的測試（記得處理除以 0 的情況），push 上去確認 CI 通過。

### 練習 3：觀察 Docker Hub

到 Docker Hub 確認你的映像檔有自動上傳，試著在本機 `docker pull` 下來跑跑看。


---

> 作者: luk  
> URL: https://yoru-karu-blog-lalaluk-52581ac5e0cef170a3c8922c19182ecb6f7bd604.gitlab.io/posts/tutorial/docker/docker-session5-cicd-github-actions/  

