GitHub ActionsCI-CDDevOps自動化部署

GitHub Actions CI/CD:自動化你的部署流程

每次發版都要手動打包、上傳、部署,不只費時還容易出錯?GitHub Actions 讓你把測試、建構、部署全部自動化——只要 push 程式碼,剩下的交給機器。這篇整理 CI/CD 基本概念與 GitHub Actions workflow 實作。

· 8 分鐘閱讀

手動部署的痛:要在正式機上 git pull、npm install、重啟服務,過程中出錯還要現場除錯。GitHub Actions 讓這一切自動化——你只需要專心寫 code,push 之後的一切(測試、建構、部署)全部由機器完成。這篇整理 CI/CD 的核心概念與 GitHub Actions 的實作方式。


前言:為什麼要自動化部署?

手動部署的問題不只是費時,更重要的是不穩定

人為操作會犯錯:可能忘記跑測試、忘記重開服務、或在正式環境做了一些實驗性變更沒有回復。CI/CD(持續整合 / 持續部署)的核心思想是:把「人」從部署流程中移除,讓機器執行定義好的流程,每次都完全一致

好處:

  • 穩定:機器執行,不會因為疲憊或分心而出錯
  • 快速:幾分鐘內完成過去可能需要半小時的手動流程
  • 可追溯:每次 deployment 都留有記錄,出了問題可以快速回溯
  • 有信心:每次 push 都經過完整測試才部署,減少正式環境的 surprise

CI/CD 整體流程圖

flowchart TD
    A[💻 Developer Push Code] --> B{GitHub Repository}
    B --> C[CI Job: 自動化測試]
    C --> D{測試通過?}
    D -->|否| E[❌ 通知 Developer]
    D -->|是| F[Build Job: 建構 Docker Image]
    F --> G[📦 Image 推送到 Registry]
    G --> H{部署目標}
    H -->|Staging| I[部署到 Staging 環境]
    H -->|Production| J[⚠️ 需要 Review]
    J -->|核准| K[部署到 Production 環境]
    J -->|拒絕| L[⛔ 取消部署]
    I --> M[✅ 部署完成]
    K --> M[✅ 部署完成]
    M --> N[📊 Deployment Report]

上圖是 CI/CD 流程的全貌:push 觸發測試 → 測試通過後建構 Image → 依據環境決定是否需要人工核准 → 部署 → 產生部署報告。


CI/CD 核心概念

持續整合(CI)

每次有人 push 或 pull request,自動執行:

  • 執行測試(Unit、Integration)
  • 執行 linter、type check
  • 建構 artifact

目標是盡快發現問題,而不是等到部署時才發現。

持續部署(CD)

CI 通過之後,自動把通過測試的版本部署到某個環境:

  • staging(測試環境)
  • production(正式環境)

這篇以 CD 到 Docker 容器為例,銜接前一篇 Docker 部署的內容。


GitHub Actions 基本結構

GitHub Actions 的設定檔放在專案的 .github/workflows/ 目錄下,格式是 YAML。

核心名詞

  • Workflow:一個 workflow 是一個自動化流程(例:CI、CD、每小时跑一次任務)
  • Job:一個 workflow 可以有多個 job,並行執行
  • Step:每個 job 有一個或多個 step,依序執行
  • Action:step 裡面執行的動作,可以是現成的 GitHub Action 或自訂腳本

最簡單的 workflow

# .github/workflows/ci.yml

name: CI

# 觸發條件:push 和 pull request 到 main 分支
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  # 單一 job,名稱叫 test
  test:
    runs-on: ubuntu-latest  # 使用 Ubuntu  runner

    steps:
      # 1. 取出程式碼
      - uses: actions/checkout@v4

      # 2. 安裝 Node.js
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'npm'  # 快取 node_modules,加速後續 run

      # 3. 安裝依賴
      - run: npm ci

      # 4. 跑 type check
      - run: npm run typecheck

      # 5. 跑測試
      - run: npm test

這個 workflow 在每次 push 或 PR 到 main 時自動執行,檢查 TypeScript 類型與測試是否通過。


建立 Docker Image 並推送到 Registry

在 CI workflow 裡加入 Docker 建構與推送:

# .github/workflows/deploy.yml

name: Build & Push Docker Image

on:
  push:
    branches: [main]
    tags:
      - 'v*'  # 當 push tag(如 v1.0.0)時也觸發

env:
  REGISTRY: ghcr.io  # GitHub Container Registry
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    
    permissions:
      contents: read
      packages: write  # 需要這個才能推送 Image 到 GHCR

    steps:
      - uses: actions/checkout@v4

      # 登入 Container Registry
      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # 從 tag 取得 Image 版本號(如 v1.0.0 → 1.0.0)
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=semver,pattern={{version}}
            type=sha,prefix=

      # 建構並推送 Image
      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

上例中 GITHUB_TOKEN 是 GitHub 自動提供的 credential,不需要另外設定。重點是 secrets.GITHUB_TOKEN 這類內建 secret,不需要手動管理。


完整的 CI/CD workflow

把測試、建構、部署串在一起:

# .github/workflows/cicd.yml

name: CI/CD Pipeline

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

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}
  TAG: ${{ github.sha }}

jobs:
  # Job 1:CI — 跑測試和檢查
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'npm'

      - run: npm ci
      - run: npm run typecheck
      - run: npm run lint
      - run: npm test

  # Job 2:建構 Docker Image(只有在 CI 成功後才執行)
  build:
    needs: ci  # 依賴 ci job
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    
    permissions:
      packages: write
      
    steps:
      - uses: actions/checkout@v4

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.TAG }}
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
          cache-from: type=gha  # 使用 GitHub Actions cache 加速建構
          cache-to: type=gha,mode=max

  # Job 3:部署到伺服器
  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    
    steps:
      - name: Deploy to server via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            # 告訴伺服器 pull 最新 Image
            docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.TAG }}
            
            # 用新 Image 重啟服務(使用 docker compose)
            cd /app/my-api
            docker compose pull
            docker compose up -d --force-recreate
            
            # 清理舊的 Image 節省空間
            docker image prune -f

部署到正式環境的 SSH key 設定

上述 workflow 中的 secrets.SERVER_SSH_KEY 需要在 GitHub 設定:

  1. 在 GitHub 專案頁面,進入 Settings → Secrets and variables → Actions
  2. New repository secret,新增:
    • SERVER_HOST:你的伺服器 IP 或網域
    • SERVER_USER:SSH 登入的使用者名稱
    • SERVER_SSH_KEY:你的 private SSH key(建議建立專門用於部署的 key,不要用個人 key)

使用 Environment 區分環境

真實專案會區分不同環境(dev、staging、production)。用 GitHub Environments 管理:

多 Environment 部署流程

flowchart LR
    subgraph Build["📦 Build Stage"]
        B1[Checkout Code] --> B2[Install Dependencies]
        B2 --> B3[TypeScript Check]
        B3 --> B4[Run Tests]
        B4 --> B5[Build Docker Image]
        B5 --> B6[Push to GHCR]
    end

    subgraph Deploy["🚀 Deploy Stage"]
        D1{Is Main Branch?}
        D1 -->|Yes| D2[Deploy to Staging]
        D2 --> D3{Staging OK?}
        D3 -->|Pass| D4[✅ Ready for Production]
        D3 -->|Fail| D5[🚨 Alert Team]
        D1 -->|No| D6[Deploy to Dev Preview]
        D6 --> D7[🔍 Developer Reviews]
        D7 --> D8{Approved?}
        D8 -->|Yes| D4
        D8 -->|No| D9[⛔ Reject]
    end

    B6 --> Deploy

    style D4 fill:#22c55e,color:#fff
    style D5 fill:#ef4444,color:#fff
    style D9 fill:#ef4444,color:#fff

上圖說明:main 分支的變更會先部署到 staging 驗證,通過後才準備進入 production;其他分支則部署到 Dev Preview 環境供開發者審查。

jobs:
  deploy-staging:
    needs: build
    runs-on: ubuntu-latest
    environment: staging  # 需要在 GitHub Settings → Environments 設定這個環境
    
    steps:
      - name: Deploy to staging
        run: echo "Deploying to staging"

  deploy-production:
    needs: build
    runs-on: ubuntu-latest
    environment: production  # 設定 require review,需要人工核准才能部署
    
    steps:
      - name: Deploy to production
        run: echo "Deploying to production"

在 GitHub → Settings → Environments 可以設定:

  • Required reviewers:部署前需要特定人員核准
  • Environment variables:該環境專用的 secret
  • Deployment branch rules:只有特定分支能部署到該環境

常見問題

Q:GitHub Actions 的免費額度用完怎麼辦?

A:GitHub Free 方案有 2000 分鐘/月的 macOS runner,Linux 和 Windows 各 2000 分鐘。公開 repository 完全免費。私人 repository 超過額度後,會暫停 workflow 或需要付費。開源專案建議設為 public,runner 分钟数无限制

Q:workflow 失敗了怎麼除錯?

A:可以在失敗的 workflow 頁面點 Re-run all jobs,或用 Re-run jobs with SSH 在 runner 裡開一個 SSH session 進去實際查看問題。日常除錯建議先用 runs-on: ubuntu-latest 的 runner,本地測試用 act(GitHub Actions local runner)工具。

Q:可以只部署特定的 commit 或 tag 嗎?

A:可以。在 on.push 設定 conditions:

on:
  push:
    branches: [main]
    tags:
      - 'v*'  # 只在 push tag 時觸發

Q:workflow 觸發太頻繁會佔用太多 minutes 嗎?

A:可以加上路徑過濾,避免無關檔案變動也觸發 workflow:

on:
  push:
    paths:
      - 'src/**'       # 只有 src/ 下的變動才觸發
      - 'package.json'
      - '.github/workflows/**'

總結:讓機器做機器擅長的事

CI/CD 的核心價值:把重複的、容易出錯的、沒有技術價值的部署工作交給機器,工程師把時間放在真正需要人的地方——寫 code、做判斷、除錯、優化

這篇文章涵蓋的範圍:

  • CI/CD 基本概念(CI 與 CD 的區別)
  • GitHub Actions 基本結構(workflow、job、step、action)
  • 完整的 CI/CD workflow(測試 → 建構 Image → 部署到伺服器)
  • GitHub Environments 與環境區分
  • secrets 管理與 SSH 部署

下一步:把前一篇 Docker + Express 做的 API 專案接上 GitHub Actions,親手體驗「push 到 main 分支後,幾分鐘內看到正式機上的服務自動更新」的成就感。


本篇是「前端工程師後端入門」系列第五篇。系列前四篇:《從 Node.js 原生 HTTP 到 Express 框架》、《RESTful API 設計:前端視角的 API 思維》、《Prisma ORM:前端工程師的第一堂資料庫課》、《Docker 部署入門:把你的應用打包上線》。