本文 AI 產出,尚未審核

FastAPI 部署與架構:CI/CD 自動部署(GitHub Actions)

簡介

在現代的 Web 開發流程中,持續整合 / 持續部署(CI/CD) 已成為不可或缺的最佳實踐。對於使用 FastAPI 建置的微服務,透過 GitHub Actions 讓程式碼在每次 push、pull request 或是發佈標籤(tag)時自動完成測試、建置 Docker 映像、並部署到雲端環境,能大幅提升開發效率、降低人為錯誤,並確保服務始終以最新且通過測試的版本上線。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,帶領讀者一步步建立 FastAPI + GitHub Actions 的全自動部署工作流程,適合剛接觸 CI/CD 的初學者,也能為已有部署經驗的開發者提供參考。


核心概念

1️⃣ 為什麼選擇 GitHub Actions?

  • 原生整合:GitHub 儲存庫即內建 Actions,免除額外服務的設定與費用。
  • 彈性工作流程:支援多種觸發條件(push、pull_request、schedule 等),且可以同時跑測試、建置、部署等多個 job。
  • 社群豐富:Marketplace 上有大量現成的 Action(如 docker/build-push-actionappleboy/ssh-action),可直接引用。

2️⃣ CI/CD 流程概觀

code commit → GitHub → GitHub Actions
   │               │
   └─> run tests ──► build Docker image
                     │
                     └─> push to registry (Docker Hub / GHCR)
                                 │
                                 └─> deploy to server / cloud

3️⃣ 必備檔案與目錄結構

my-fastapi-app/
│
├─ app/                 # FastAPI 程式碼
│   └─ main.py
├─ tests/               # pytest 測試
│   └─ test_main.py
├─ Dockerfile           # 建置映像
├─ requirements.txt
└─ .github/
   └─ workflows/
       └─ ci-cd.yml     # GitHub Actions 工作流程

4️⃣ 主要技術

  • pytest:執行單元測試。
  • Docker:將 FastAPI 包裝成可移植的容器。
  • GitHub Container Registry (GHCR):存放私有 Docker 映像。
  • SSH / SCP:將映像部署至自建伺服器(亦可改用 AWS ECS、Google Cloud Run 等)。

程式碼範例

4.1 Dockerfile(建置 FastAPI 映像)

# Dockerfile
FROM python:3.11-slim

# 建立非 root 使用者以提升安全性
RUN useradd -m appuser
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY ./app ./app

# 使用非 root 使用者執行
USER appuser

EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

註解

  • python:3.11-slim 體積小、適合生產環境。
  • USER appuser 可避免容器內程式以 root 身份執行,降低安全風險。

4.2 pytest 範例(測試 FastAPI 路由)

# tests/test_main.py
from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_read_root():
    """檢查根路由是否回傳 200 與預期訊息"""
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Hello, FastAPI!"}

說明

  • TestClient 讓我們在不啟動實體伺服器的情況下直接呼叫 API。
  • 測試失敗會直接讓 GitHub Actions 中的 test job 失敗,阻止後續部署。

4.3 GitHub Actions 工作流程(ci-cd.yml)

# .github/workflows/ci-cd.yml
name: CI/CD for FastAPI

on:
  push:
    branches: [ main ]
    tags: [ 'v*' ]          # 只在標籤為 v* 時執行部署
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install pytest

      - name: Run tests
        run: pytest -q

  build-and-push:
    needs: test               # 必須在 test 成功後才執行
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')   # 只在打 tag 時推送映像
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository_owner }}/fastapi-app:${{ github.ref_name }}

  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
    steps:
      - name: Deploy to remote server via SSH
        uses: appleboy/ssh-action@v0.1.10
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            docker pull ghcr.io/${{ github.repository_owner }}/fastapi-app:${{ github.ref_name }}
            docker stop fastapi-app || true
            docker rm fastapi-app || true
            docker run -d --name fastapi-app -p 80:8000 ghcr.io/${{ github.repository_owner }}/fastapi-app:${{ github.ref_name }}

重點說明

  1. 測試階段 (test) 必須全部通過,否則後續建置與部署不會執行。
  2. 建置與推送 只在打上 v* 標籤(如 v1.0.0)時觸發,避免每次 push 都上傳映像。
  3. 部署階段 透過 appleboy/ssh-action 在遠端伺服器上拉取最新映像、停止舊容器、重新啟動。
  4. 所有機密資訊(SSH 金鑰、伺服器 IP)均放在 GitHub Secrets,絕不硬編碼在程式碼內。

4.4 使用 GitHub Secrets 儲存機密

Secret 名稱 用途說明
GITHUB_TOKEN 內建 token,可登入 GHCR(無需額外設定)
SERVER_HOST 部署目標伺服器 IP 或域名
SERVER_USER SSH 登入帳號
SERVER_SSH_KEY 私密金鑰(Base64 編碼或直接貼上)

在 GitHub Repo → Settings → Secrets and variables → Actions 中新增上述項目。


常見陷阱與最佳實踐

陷阱 說明 解決方式
測試未覆蓋 只跑 pytest,但測試覆蓋率低,可能讓錯誤程式碼通過 CI。 加入 pytest-cov,在 test job 中使用 --cov=app --cov-fail-under=80 讓覆蓋率低於 80% 時失敗。
Docker cache 失效 每次建置都重新安裝依賴,耗時長。 docker/build-push-action 中使用 cache-fromcache-to,或在 Dockerfile 中把 requirements.txt 先 copy 再 install,利用層級快取。
機密資訊寫死 把 SSH 金鑰寫在 workflow 檔案裡,會被公開。 務必使用 GitHub Secrets,且在 workflow 中僅以 ${{ secrets.xxx }} 讀取。
標籤格式不一致 打錯標籤(如 v1)導致部署腳本找不到正確 tag。 統一使用 vMAJOR.MINOR.PATCH,可在 CI 中加入正則驗證。
容器未健康檢查 部署後容器啟動失敗,但 Actions 仍顯示成功。 在 Dockerfile 加入 HEALTHCHECK,或在部署腳本使用 docker ps 檢查容器狀態。

最佳實踐

  • 分支策略main 為穩定分支,所有 PR 必須通過測試才能 merge。
  • 標籤發布:使用 git tag -a v1.0.0 -m "Release 1.0.0",再 git push origin v1.0.0 觸發部署。
  • 日誌集中:將容器日誌輸出到 stdout,配合雲端日誌服務(如 CloudWatch、Grafana Loki)統一監控。
  • 藍綠部署:若需求較高,可在 deploy job 中先啟動新容器(fastapi-app-new),測試成功後再切流量,最後移除舊容器。

實際應用場景

  1. 小型 SaaS 服務
    開發團隊每天多次提交功能,透過 GitHub Actions 自動跑測試、建置映像,並在每次正式版(v*)發布時自動部署至 AWS EC2。開發者只需要在本地 git push 即可完成全流程。

  2. 內部微服務平台
    企業內部有多個 FastAPI 微服務,每個服務都有自己的 repo。使用相同的 CI/CD 模板,統一管理 Docker 映像與部署腳本,降低維運成本。

  3. 教育或開源專案
    想要讓使用者快速體驗專案,只要在 GitHub 上打上一個 Release 標籤,GitHub Actions 會自動把最新映像推到 GHCR,使用者即可透過 docker run 直接執行。


總結

  • CI/CD 是 FastAPI 專案走向自動化、可持續交付的關鍵。*
  • GitHub Actions 提供了從 測試 → Docker 建置 → 映像推送 → 部署 的完整流水線,只要妥善設定 workflow、使用 Secrets 保存機密,即可在每次標籤發布時自動將最新的 FastAPI 服務上線。*
  • 透過本文的範例與最佳實踐,你可以快速在自己的專案中建立穩定且安全的自動部署機制,從而把更多時間投入在 業務邏輯API 設計 上,而不是繁雜的手動部署工作。

祝你在 FastAPI 的部署之路上一路順風,持續交付高品質的 API! 🚀