Docker 教學 第 6 堂:GitHub Actions 進階實作與課程總結

6-1 GitHub Actions 進階語法(約 1 小時)

環境變數

在 workflow 裡可以用環境變數來避免重複寫相同的值:

name: CI with Environment Variables

on:
  push:
    branches: [ main ]

env:                           # 整個 workflow 共用的環境變數
  IMAGE_NAME: my-flask-app
  PYTHON_VERSION: '3.11'

jobs:
  build:
    runs-on: ubuntu-latest

    env:                       # 這個 job 專用的環境變數
      FLASK_ENV: testing

    steps:
      - name: 顯示環境變數
        run: |
          echo "Image: ${{ env.IMAGE_NAME }}"
          echo "Python: ${{ env.PYTHON_VERSION }}"
          echo "Flask: $FLASK_ENV"          

環境變數的三個層級:

層級範圍寫在哪裡
Workflow 層所有 job 都能用最外層的 env:
Job 層只有這個 job 能用job 底下的 env:
Step 層只有這個 step 能用step 底下的 env:

條件判斷

可以根據條件決定要不要執行某個 step 或 job:

steps:
  - name: 只在 main 分支執行
    if: github.ref == 'refs/heads/main'
    run: echo "This is main branch"

  - name: 只在 PR 時執行
    if: github.event_name == 'pull_request'
    run: echo "This is a pull request"

  - name: 前一步失敗才執行(通知用)
    if: failure()
    run: echo "Something went wrong!"

多個 Job 的依賴關係

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Running tests..."

  build:
    runs-on: ubuntu-latest
    needs: test                # 等 test 通過才跑
    steps:
      - run: echo "Building..."

  deploy:
    runs-on: ubuntu-latest
    needs: build               # 等 build 完成才跑
    steps:
      - run: echo "Deploying..."

執行順序:

test → build → deploy
(任何一步失敗,後面的都不會跑)

手動觸發 Workflow

除了 push 自動觸發,也可以加上手動觸發的按鈕:

on:
  push:
    branches: [ main ]
  workflow_dispatch:           # 加上這行就能手動觸發
    inputs:
      environment:
        description: '部署環境'
        required: true
        default: 'staging'
        type: choice
        options:
          - staging
          - production

在 GitHub 的 Actions 頁面就會出現「Run workflow」按鈕,可以選擇參數後手動執行。


6-2 實作:完整的 Flask CI/CD Pipeline(約 1.5 小時)

目標

建立一個完整的 CI/CD 流程:

  1. Push 程式碼到 GitHub
  2. 自動跑測試
  3. 測試通過 → 自動 build Docker Image
  4. 自動 push 到 Docker Hub
  5. 輸出部署資訊

步驟 1:建立 Flask 專案

建立新的專案資料夾或使用之前的 docker-cicd-demo

app.py

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/')
def hello():
    return jsonify({
        'message': 'Hello from CI/CD!',
        'version': '1.0.0'
    })

@app.route('/health')
def health():
    return jsonify({'status': 'healthy'})

def add(a, b):
    return a + b

def is_positive(n):
    return n > 0

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

test_app.py

import pytest
from app import app, add, is_positive

@pytest.fixture
def client():
    app.config['TESTING'] = True
    with app.test_client() as client:
        yield client

def test_hello(client):
    response = client.get('/')
    assert response.status_code == 200
    data = response.get_json()
    assert 'message' in data

def test_health(client):
    response = client.get('/health')
    assert response.status_code == 200
    data = response.get_json()
    assert data['status'] == 'healthy'

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

def test_is_positive():
    assert is_positive(1) == True
    assert is_positive(-1) == False
    assert is_positive(0) == False

requirements.txt

flask
pytest

Dockerfile

FROM python:3.11-slim

WORKDIR /app

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

COPY . .

EXPOSE 5000

CMD ["python", "app.py"]

.dockerignore

.git
.github
__pycache__
*.pyc
venv/
.env
*.md
test_*.py

步驟 2:建立完整的 CI/CD Workflow

.github/workflows/cicd.yml

name: Flask CI/CD Pipeline

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

env:
  IMAGE_NAME: docker-cicd-demo

jobs:
  # ===== Job 1: 測試 =====
  test:
    runs-on: ubuntu-latest

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

      - name: 安裝 Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

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

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

  # ===== Job 2: Build 並 Push Docker Image =====
  docker:
    runs-on: ubuntu-latest
    needs: test                    # 測試通過才跑
    if: github.event_name == 'push'  # 只有 push 才 build,PR 不 build

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

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

      - name: 取得版本資訊
        id: version
        run: |
          echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
          echo "date=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT          

      - name: Build 並 Push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest
            ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.sha_short }}            

      - name: 輸出部署資訊
        run: |
          echo "Docker Image 已推送!"
          echo "Image: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}"
          echo "Tags: latest, ${{ steps.version.outputs.sha_short }}"
          echo ""
          echo "要在伺服器上部署,執行:"
          echo "docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest"
          echo "docker run -d -p 5000:5000 ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest"          

步驟 3:Push 並觀察完整流程

git add .
git commit -m "add complete CI/CD pipeline"
git push

到 GitHub Actions 頁面觀察:

test(跑測試)
  ↓ 通過
docker(build + push)
  ↓ 完成
Docker Hub 上出現新的映像檔

步驟 4:驗證映像檔可以使用

# 從 Docker Hub 拉下來
docker pull 你的帳號/docker-cicd-demo:latest

# 跑起來
docker run -d --name cicd-test -p 5000:5000 你的帳號/docker-cicd-demo:latest

# 測試
curl http://localhost:5000
curl http://localhost:5000/health

# 清理
docker rm -f cicd-test

6-3 GitHub Actions 實用技巧(約 15 分鐘)

Badge:在 README 顯示 CI 狀態

README.md 加上一行,就能顯示 CI 是通過還是失敗的徽章:

![CI](https://github.com/你的帳號/docker-cicd-demo/actions/workflows/cicd.yml/badge.svg)

效果:別人一進你的 Repository 就能看到目前 CI 狀態是綠色(通過)還是紅色(失敗)。

快取依賴加速 CI

每次 CI 都要重新 pip install 很慢,可以加上快取:

      - name: 快取 pip 套件
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}

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

第一次跑會正常安裝,之後只要 requirements.txt 沒變,就會用快取,速度快很多。

多版本測試(Matrix)

想同時在多個 Python 版本上測試:

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.9', '3.10', '3.11', '3.12']

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - run: pip install -r requirements.txt
      - run: pytest test_app.py -v

這會同時跑 4 個 job,分別在 Python 3.9、3.10、3.11、3.12 上測試。


6-4 課程總結(約 15 分鐘)

六堂課回顧

堂次主題學到的重點
1Docker 基礎概念與安裝Docker 是什麼、容器 vs VM、安裝 Docker Desktop
2映像檔與容器操作pull、run、ps、stop、rm、Volume、端口映射
3Docker Compose 與進階操作compose、MySQL、多容器管理、情境題
4Dockerfile 與映像檔建構Dockerfile 語法、分層、優化、push 到 Docker Hub
5CI/CD 與 GitHub Actions 入門CI/CD 概念、workflow 語法、自動測試、自動 build
6GitHub Actions 進階實作完整 pipeline、環境變數、快取、多版本測試

完整的開發到部署流程

寫程式碼 → git push → GitHub Actions 自動跑測試
                         ↓ 測試通過
                    自動 build Docker Image
                         ↓
                    自動 push 到 Docker Hub
                         ↓
                    部署到伺服器
                         ↓
                    使用者看到最新版本

接下來可以學什麼?

方向工具 / 技術
容器編排Kubernetes(K8s)、Docker Swarm
雲端部署AWS ECS、GCP Cloud Run、Azure Container
監控Prometheus、Grafana、ELK Stack
進階 CI/CDArgoCD、GitOps

課堂練習

練習 1:加上健康檢查測試

在 workflow 裡加一個 step,build 完 Docker Image 之後先在 CI 環境裡跑起來,用 curl 測試 /health endpoint 確認容器正常運行,再 push 到 Docker Hub。

練習 2:手動觸發部署

修改 workflow,加上 workflow_dispatch,讓你可以手動點按鈕觸發部署,並選擇要部署到 staging 還是 production。

練習 3:完整專案挑戰

建立一個完整的 Flask + MySQL 專案(用 Docker Compose),搭配 GitHub Actions 實現:

  1. Push 時自動跑測試
  2. 測試通過自動 build 並 push 映像檔
  3. 在 README 加上 CI 狀態徽章
0%