GitHub Actions 完全指南

深入理解 GitHub Actions 的核心概念、Workflow 語法、觸發機制、可重用元件與實戰應用


目錄


什麼是 GitHub Actions?

GitHub Actions 是 GitHub 內建的 CI/CD 和自動化平台,讓你在 Repository 中直接定義自動化流程,回應事件(push、PR、排程等)來執行建構、測試、部署等任務。

核心特點

  • 事件驅動:push、PR、issue、排程、手動觸發等都可作為啟動條件
  • YAML 定義:用 .github/workflows/*.yml 宣告式地描述整個流程
  • 豐富的 Marketplace:超過 20,000+ 個社群 Action 可直接使用
  • 矩陣建構:一份設定同時跑多個版本/平台的組合測試
  • 原生整合:與 GitHub 的 PR、Issue、Package、Environment 深度整合

GitHub Actions vs 其他 CI/CD 工具

特性 GitHub Actions GitLab CI/CD Jenkins
設定方式 YAML(Repository 內) YAML(Repository 內) Groovy / UI
託管 GitHub 託管 Runner GitLab 託管 Runner 自行架設
費用 公開 Repo 免費 公開 Repo 免費 開源免費(需自行維護)
上手難度 中高
擴充性 Marketplace Action CI 模板 Plugin 生態系
Self-hosted ✅ 支援 ✅ 支援 ✅ 原生

核心概念

架構總覽

Repository
└── .github/workflows/
    ├── ci.yml          ← Workflow(工作流程)
    ├── deploy.yml
    └── release.yml

每個 Workflow:
┌──────────────────────────────────────────────┐
│  Workflow(工作流程)                          │
│                                              │
│  ┌──────────┐   ┌──────────┐                 │
│  │  Job A   │──→│  Job B   │  ← 可定義依賴     │
│  │          │   │          │                 │
│  │  Step 1  │   │  Step 1  │  ← 每個 Step     │
│  │  Step 2  │   │  Step 2  │    是一個 Action │
│  │  Step 3  │   │  Step 3  │    或 Shell 指令 │
│  └──────────┘   └──────────┘                 │
│   Runner A       Runner B    ← 各自獨立的機器  │
└──────────────────────────────────────────────┘

六大元件

元件 說明 比喻
Workflow 自動化流程的最上層單位,一個 .yml 檔案就是一個 Workflow 一條生產線
Event 觸發 Workflow 的事件(push、PR、排程等) 啟動開關
Job Workflow 中的一組任務,每個 Job 在獨立 Runner 上執行 生產線上的工作站
Step Job 中的單一操作步驟,按順序執行 工作站上的單一動作
Action 可重用的最小單元,可以是社群的或自定義的 標準化的工具
Runner 執行 Job 的伺服器(GitHub 託管或自架) 工作機器

元件關係

Event(觸發)
  └→ Workflow(流程)
       ├→ Job 1(獨立 Runner)
       │    ├→ Step 1: uses: actions/checkout@v4    ← Action
       │    ├→ Step 2: run: npm install             ← Shell 指令
       │    └→ Step 3: run: npm test
       └→ Job 2(依賴 Job 1)
            ├→ Step 1: uses: actions/checkout@v4
            └→ Step 2: run: npm run deploy

Runner 類型

Runner 作業系統 標籤 適用場景
GitHub 託管 Ubuntu ubuntu-latestubuntu-24.04 通用 CI/CD
GitHub 託管 macOS macos-latestmacos-15 iOS / macOS 建構
GitHub 託管 Windows windows-latestwindows-2022 .NET / Windows 建構
Self-hosted 自訂 自訂標籤 特殊硬體、私有網路、降低費用

Workflow 語法詳解

基本結構

# .github/workflows/ci.yml
name: CI                          # Workflow 名稱(顯示在 GitHub UI)

on:                               # 觸發條件
  push:
    branches: [main]

permissions:                      # Workflow 層級的權限設定
  contents: read

env:                              # Workflow 層級的環境變數
  NODE_VERSION: '20'

jobs:                             # 定義所有 Job
  build:                          # Job ID(自訂名稱)
    name: Build Application       # Job 顯示名稱
    runs-on: ubuntu-latest        # Runner 類型
    timeout-minutes: 10           # 超時設定

    env:                          # Job 層級的環境變數
      CI: true

    steps:                        # 定義步驟
      - name: Checkout            # Step 名稱
        uses: actions/checkout@v4 # 使用 Action

      - name: Build
        run: npm run build        # 執行 Shell 指令

Step 的兩種類型

steps:
  # 類型 1:使用 Action(uses)
  - name: Setup Node.js
    uses: actions/setup-node@v4       # {owner}/{repo}@{ref}
    with:                             # 傳入 Action 的參數
      node-version: '20'
      cache: 'npm'

  # 類型 2:執行 Shell 指令(run)
  - name: Install and Build
    run: |                            # 多行指令用 |
      npm ci
      npm run build
    working-directory: ./frontend     # 指定工作目錄
    shell: bash                       # 指定 Shell(預設 bash)
    env:                              # Step 層級的環境變數
      NODE_ENV: production

條件執行(if)

steps:
  # 只在 main 分支執行
  - name: Deploy
    if: github.ref == 'refs/heads/main'
    run: ./deploy.sh

  # 只在 PR 執行
  - name: Preview
    if: github.event_name == 'pull_request'
    run: ./preview.sh

  # 即使前一步失敗也執行(常用於清理)
  - name: Cleanup
    if: always()
    run: ./cleanup.sh

  # 只在失敗時執行(通知)
  - name: Notify on failure
    if: failure()
    run: ./notify-slack.sh

  # 組合條件
  - name: Release
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    run: ./release.sh

Step 之間傳遞資料

jobs:
  example:
    runs-on: ubuntu-latest
    steps:
      # 方式 1:透過 output
      - name: Set version
        id: version                   # 設定 Step ID
        run: echo "value=1.2.3" >> $GITHUB_OUTPUT

      - name: Use version
        run: echo "Version is ${{ steps.version.outputs.value }}"

      # 方式 2:透過環境變數
      - name: Set env
        run: echo "MY_VAR=hello" >> $GITHUB_ENV

      - name: Use env
        run: echo "$MY_VAR"           # 直接使用

觸發事件

常用觸發事件總覽

on:
  # ── Git 事件 ──
  push:                               # 推送到分支
    branches: [main, develop]
    tags: ['v*']                       # 推送標籤
    paths: ['src/**']                  # 只在特定路徑變更時觸發

  pull_request:                        # PR 事件
    branches: [main]
    types: [opened, synchronize, reopened]

  # ── 排程 ──
  schedule:
    - cron: '0 2 * * 1'               # 每週一凌晨 2 點(UTC)

  # ── 手動觸發 ──
  workflow_dispatch:                   # 在 GitHub UI 手動觸發
    inputs:
      environment:
        description: '部署環境'
        required: true
        type: choice
        options: [staging, production]
      dry_run:
        description: '僅測試不實際部署'
        type: boolean
        default: false

  # ── 其他 Workflow 觸發 ──
  workflow_call:                       # 被其他 Workflow 呼叫(可重用 Workflow)
    inputs:
      config:
        type: string
        required: true
    secrets:
      deploy_key:
        required: true

路徑過濾

on:
  push:
    branches: [main]
    paths:
      - 'src/**'             # src 下任何變更
      - '!src/**/*.test.js'  # 排除測試檔案
      - 'package.json'       # 特定檔案
    paths-ignore:             # 或用 paths-ignore 排除
      - 'docs/**'
      - '*.md'

活動類型過濾

on:
  # PR 的特定動作
  pull_request:
    types: [opened, synchronize, reopened, labeled]

  # Issue 的特定動作
  issues:
    types: [opened, labeled]

  # Release 發布
  release:
    types: [published]

  # 外部觸發(API 呼叫)
  repository_dispatch:
    types: [deploy-command]

Cron 語法速查

┌───────────── 分鐘(0-59)
│ ┌─────────── 小時(0-23)
│ │ ┌───────── 日期(1-31)
│ │ │ ┌─────── 月份(1-12)
│ │ │ │ ┌───── 星期幾(0-6,0 = 星期日)
│ │ │ │ │
* * * * *
範例 說明
0 0 * * * 每天 00:00 UTC
0 2 * * 1 每週一 02:00 UTC
30 5 1 * * 每月 1 號 05:30 UTC
*/15 * * * * 每 15 分鐘
0 9 * * 1-5 週一到週五 09:00 UTC

⚠️ GitHub Actions 的 cron 使用 UTC 時區,台灣時間需要 減 8 小時。 例如想在台灣時間每天早上 9 點執行,cron 要設定為 0 1 * * *(UTC 01:00)。


表達式與上下文

表達式語法

GitHub Actions 使用 ${{ }} 語法存取上下文資料和進行運算。

# 基本存取
${{ github.ref }}
${{ secrets.API_KEY }}
${{ env.NODE_VERSION }}

# 比較運算
${{ github.ref == 'refs/heads/main' }}
${{ github.event_name != 'pull_request' }}

# 邏輯運算
${{ github.ref == 'refs/heads/main' && success() }}
${{ failure() || cancelled() }}

# 包含判斷
${{ contains(github.event.head_commit.message, '[skip ci]') }}
${{ startsWith(github.ref, 'refs/tags/v') }}

# 三元運算(用 && || 模擬)
${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}

常用上下文

上下文 說明 常用屬性
github 事件與 Repository 資訊 refshaevent_namerepositoryactor
env 環境變數 自訂的環境變數
secrets 加密的 Secrets GITHUB_TOKEN、自訂 Secrets
jobs 目前 Workflow 的 Job 資訊 jobs.<id>.result
steps 目前 Job 的 Step 資訊 steps.<id>.outputssteps.<id>.outcome
matrix 矩陣參數 矩陣定義的變數
needs 依賴 Job 的資訊 needs.<id>.outputsneeds.<id>.result
inputs 手動觸發或可重用 Workflow 的輸入 自訂的 input
runner Runner 環境資訊 osarchtemp
vars Repository / Organization 層級變數 自訂的變數

狀態檢查函數

if: success()      # 前面的步驟都成功(預設行為)
if: failure()      # 前面有步驟失敗
if: always()       # 無論成功或失敗都執行
if: cancelled()    # Workflow 被取消時

Job 進階配置

Job 之間的依賴

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm run lint

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm test

  # 依賴 lint 和 test 都成功後才執行
  deploy:
    needs: [lint, test]             # 等待這些 Job 完成
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh
         ┌──────┐
    ┌───→│ lint │───┐
    │    └──────┘   │
觸發─┤              ├──→ deploy
    │    ┌──────┐   │
    └───→│ test │───┘
         └──────┘
   (lint 和 test 平行執行,都成功後才 deploy)

Job 之間傳遞資料

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:                            # 宣告 Job 的 output
      version: ${{ steps.ver.outputs.version }}
    steps:
      - id: ver
        run: echo "version=1.2.3" >> $GITHUB_OUTPUT

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploying ${{ needs.build.outputs.version }}"

Matrix 策略(矩陣建構)

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        node-version: [18, 20, 22]
        # 產生 3 × 3 = 9 個組合

        # 排除特定組合
        exclude:
          - os: windows-latest
            node-version: 18

        # 額外加入特定組合
        include:
          - os: ubuntu-latest
            node-version: 22
            experimental: true        # 自訂變數

      fail-fast: false                # 一個失敗不中止其他(預設 true)
      max-parallel: 4                 # 最大同時執行數

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm test

Services(服務容器)

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:                       # 自訂服務名稱
        image: postgres:16
        env:
          POSTGRES_PASSWORD: testpass
          POSTGRES_DB: testdb
        ports:
          - 5432:5432
        options: >-                   # Docker 選項
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

      redis:
        image: redis:7
        ports:
          - 6379:6379

    steps:
      - uses: actions/checkout@v4
      - name: Run tests
        env:
          DATABASE_URL: postgres://postgres:testpass@localhost:5432/testdb
          REDIS_URL: redis://localhost:6379
        run: npm test

權限設定(Permissions)

# Workflow 層級
permissions:
  contents: read          # 讀取程式碼
  pull-requests: write    # 寫入 PR 評論
  issues: write           # 建立 Issue

# Job 層級(覆蓋 Workflow 層級)
jobs:
  deploy:
    permissions:
      contents: read
      id-token: write     # OIDC token(用於雲端部署)

Concurrency(並行控制)

# Workflow 層級
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true    # 新的執行會取消進行中的

# Job 層級
jobs:
  deploy:
    concurrency:
      group: deploy-${{ github.ref }}
      cancel-in-progress: false   # 部署不應被取消

Environment(部署環境)

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment:
      name: staging
      url: https://staging.example.com    # 顯示在 PR 上的連結
    steps:
      - run: ./deploy.sh staging

  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment:
      name: production                    # 可在 GitHub 設定必要審核
      url: https://example.com
    steps:
      - run: ./deploy.sh production

在 GitHub Repository → Settings → Environments 中可設定:

  • Required reviewers:部署前需人工審核
  • Wait timer:等待一段時間後自動通過
  • Branch protection:限制只有特定分支能部署

可重用元件

可重用 Workflow(Reusable Workflows)

定義(被呼叫的 Workflow):

# .github/workflows/reusable-deploy.yml
name: Reusable Deploy

on:
  workflow_call:                       # 宣告為可重用 Workflow
    inputs:
      environment:
        required: true
        type: string
      version:
        required: true
        type: string
    secrets:
      deploy_key:
        required: true
    outputs:
      deploy_url:
        description: "部署後的 URL"
        value: ${{ jobs.deploy.outputs.url }}

jobs:
  deploy:
    runs-on: ubuntu-latest
    outputs:
      url: ${{ steps.deploy.outputs.url }}
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v4
      - name: Deploy
        id: deploy
        run: |
          echo "Deploying ${{ inputs.version }} to ${{ inputs.environment }}"
          echo "url=https://${{ inputs.environment }}.example.com" >> $GITHUB_OUTPUT
        env:
          DEPLOY_KEY: ${{ secrets.deploy_key }}

呼叫

# .github/workflows/release.yml
name: Release

on:
  push:
    tags: ['v*']

jobs:
  deploy-staging:
    uses: ./.github/workflows/reusable-deploy.yml    # 呼叫可重用 Workflow
    with:
      environment: staging
      version: ${{ github.ref_name }}
    secrets:
      deploy_key: ${{ secrets.STAGING_KEY }}

  deploy-production:
    needs: deploy-staging
    uses: ./.github/workflows/reusable-deploy.yml
    with:
      environment: production
      version: ${{ github.ref_name }}
    secrets:
      deploy_key: ${{ secrets.PRODUCTION_KEY }}

Composite Action(組合動作)

將多個步驟封裝成一個可重用的 Action。

# .github/actions/setup-and-build/action.yml
name: 'Setup and Build'
description: '設定環境並建構專案'

inputs:
  node-version:
    description: 'Node.js 版本'
    required: false
    default: '20'

outputs:
  build-path:
    description: '建構輸出路徑'
    value: ${{ steps.build.outputs.path }}

runs:
  using: 'composite'                   # 宣告為 Composite Action
  steps:
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}
        cache: 'npm'

    - name: Install dependencies
      shell: bash                      # composite 中 run 必須指定 shell
      run: npm ci

    - name: Build
      id: build
      shell: bash
      run: |
        npm run build
        echo "path=dist" >> $GITHUB_OUTPUT

使用

steps:
  - uses: actions/checkout@v4
  - uses: ./.github/actions/setup-and-build   # 相對路徑引用
    with:
      node-version: '20'

Reusable Workflow vs Composite Action

特性 Reusable Workflow Composite Action
粒度 整個 Workflow(含多個 Job) 單一 Step(多步驟組合)
Runner 各自獨立的 Runner 使用呼叫方的 Runner
Secrets 需要明確傳遞 自動繼承呼叫方的上下文
檔案位置 .github/workflows/ .github/actions/{name}/
適用場景 完整的部署流程 重複的設定步驟

Secrets 與環境變數

Secrets 層級

Organization Secrets     ← 所有 Repo 共用
Repository Secrets       ← 單一 Repo 內共用
Environment Secrets      ← 特定 Environment 內使用

使用方式

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Deploy
        env:
          # Secrets 透過 env 傳入
          API_KEY: ${{ secrets.API_KEY }}
          # 內建的 GITHUB_TOKEN(自動提供)
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: ./deploy.sh

      - name: Use vars
        env:
          # Repository Variables(非加密,適合非敏感設定)
          APP_NAME: ${{ vars.APP_NAME }}
        run: echo "Deploying $APP_NAME"

Secrets vs Variables

Secrets Variables
加密 ✅ 加密儲存 ❌ 明文儲存
Log 遮罩 ✅ 自動遮罩 *** ❌ 會顯示在 Log
用途 API Key、Token、密碼 環境名稱、版本號、功能開關
存取 ${{ secrets.NAME }} ${{ vars.NAME }}

GITHUB_TOKEN

每次 Workflow 執行時自動產生的 Token,用來存取當前 Repository。

permissions:
  contents: read
  pull-requests: write

steps:
  - name: Comment on PR
    uses: actions/github-script@v7
    with:
      github-token: ${{ secrets.GITHUB_TOKEN }}
      script: |
        github.rest.issues.createComment({
          owner: context.repo.owner,
          repo: context.repo.repo,
          issue_number: context.issue.number,
          body: '✅ CI 通過!'
        })

快取與效能優化

依賴快取

# 方式 1:使用 setup-* Action 內建的 cache
- uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'npm'                      # 自動快取 node_modules

# 方式 2:手動控制快取
- uses: actions/cache@v4
  with:
    path: |
      ~/.npm
      node_modules
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-

Artifact(建構產出物)

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm run build

      # 上傳 Artifact
      - uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: dist/
          retention-days: 7           # 保留天數(預設 90)

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      # 下載 Artifact
      - uses: actions/download-artifact@v4
        with:
          name: build-output
          path: dist/
      - run: ./deploy.sh dist/

效能優化技巧

技巧 說明 節省效果
依賴快取 快取 node_modules、Go modules 等 安裝時間減少 50-80%
paths 過濾 只在相關檔案變更時觸發 減少不必要的執行
concurrency 取消進行中的舊 Workflow 節省 Runner 時間
矩陣 fail-fast 一個失敗就中止全部 快速回饋
Job 平行化 無依賴的 Job 同時執行 總時間 = 最長的 Job
Docker layer cache 快取 Docker 建構層 建構時間減少 60-90%
Artifact 傳遞 Job 之間傳遞建構結果 避免重複建構

實戰範例

範例 1:完整的 CI Pipeline

# .github/workflows/ci.yml
name: CI

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

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run lint
      - run: npx tsc --noEmit           # 型別檢查

  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: test
          POSTGRES_DB: testdb
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm test -- --coverage
        env:
          DATABASE_URL: postgres://postgres:test@localhost:5432/testdb
      - uses: codecov/codecov-action@v4
        if: github.event_name == 'push'

  build:
    needs: [lint, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: build
          path: dist/

範例 2:Docker 建構 + 多環境部署

# .github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches: [main]
    tags: ['v*']

permissions:
  contents: read
  packages: write

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

jobs:
  build-image:
    runs-on: ubuntu-latest
    outputs:
      image-tag: ${{ steps.meta.outputs.version }}
    steps:
      - uses: actions/checkout@v4

      - uses: docker/setup-buildx-action@v3

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

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=sha,prefix=

      - uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy-staging:
    needs: build-image
    runs-on: ubuntu-latest
    environment:
      name: staging
      url: https://staging.example.com
    steps:
      - name: Deploy to staging
        run: |
          echo "Deploying ${{ needs.build-image.outputs.image-tag }} to staging"

  deploy-production:
    needs: [build-image, deploy-staging]
    if: startsWith(github.ref, 'refs/tags/v')
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://example.com
    steps:
      - name: Deploy to production
        run: |
          echo "Deploying ${{ needs.build-image.outputs.image-tag }} to production"

範例 3:手動觸發 + 排程任務

# .github/workflows/maintenance.yml
name: Maintenance

on:
  schedule:
    - cron: '0 1 * * 1'               # 每週一台灣時間 09:00
  workflow_dispatch:
    inputs:
      task:
        description: '執行任務'
        required: true
        type: choice
        options:
          - cleanup
          - backup
          - health-check

jobs:
  maintenance:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run task
        run: |
          TASK="${{ github.event.inputs.task || 'health-check' }}"
          echo "Running: $TASK"
          ./scripts/maintenance.sh "$TASK"

      - name: Notify
        if: failure()
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {"text": "⚠️ 維護任務失敗:${{ github.event.inputs.task }}"}
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

範例 4:PR 自動化

# .github/workflows/pr-automation.yml
name: PR Automation

on:
  pull_request:
    types: [opened, synchronize, labeled]

permissions:
  contents: read
  pull-requests: write

jobs:
  auto-label:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/labeler@v5
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}

  size-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Check PR size
        uses: actions/github-script@v7
        with:
          script: |
            const { data: files } = await github.rest.pulls.listFiles({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: context.issue.number,
            });

            const changes = files.reduce((sum, f) => sum + f.changes, 0);

            if (changes > 500) {
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.issue.number,
                body: `⚠️ 這個 PR 有 ${changes} 行變更,建議拆分為更小的 PR。`,
              });
            }

安全性最佳實踐

1. Action 版本鎖定

# ✅ 推薦:使用 commit SHA(最安全)
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2

# ✅ 可接受:使用主版本標籤
- uses: actions/checkout@v4

# ❌ 不推薦:使用 main 分支(可能被竄改)
- uses: actions/checkout@main

2. 最小權限原則

# Workflow 層級設定最小權限
permissions:
  contents: read

jobs:
  deploy:
    permissions:
      contents: read
      id-token: write     # 只在需要的 Job 開放

3. Secret 安全

# ❌ 不要在指令中直接使用 Secret
- run: curl -H "Authorization: ${{ secrets.API_KEY }}" https://api.example.com

# ✅ 透過環境變數傳入
- run: curl -H "Authorization: $API_KEY" https://api.example.com
  env:
    API_KEY: ${{ secrets.API_KEY }}

4. Fork PR 的安全考量

# 防止 Fork 的 PR 存取 Secrets
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm test

  # 只在非 Fork PR 執行需要 Secrets 的操作
  deploy-preview:
    if: github.event.pull_request.head.repo.full_name == github.repository
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy-preview.sh
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

5. 安全檢查清單

項目 說明
鎖定 Action 版本 使用 SHA 或主版本標籤,避免 @main
最小權限 permissions 只開需要的
保護 Secrets 不要在 run 中直接展開,用 env 傳入
Fork 隔離 對 Fork PR 限制 Secrets 和敏感操作
Branch Protection 主分支要求 CI 通過才能合併
Environment 審核 Production 環境設定必要審核人
OIDC 雲端部署用 OIDC 取代長期 Token

除錯與疑難排解

啟用 Debug Log

在 Repository → Settings → Secrets 新增:

Secret 用途
ACTIONS_RUNNER_DEBUG true Runner 層級的除錯日誌
ACTIONS_STEP_DEBUG true Step 層級的除錯日誌

本地除錯工具:act

# 安裝
brew install act

# 執行 Workflow
act push                          # 模擬 push 事件
act pull_request                  # 模擬 PR 事件
act -j build                     # 只執行特定 Job
act -l                            # 列出所有 Workflow 和 Job
act --secret-file .secrets        # 使用本地 Secrets 檔案

常見除錯技巧

steps:
  # 印出所有上下文資訊
  - name: Debug context
    run: |
      echo "Event: ${{ github.event_name }}"
      echo "Ref: ${{ github.ref }}"
      echo "SHA: ${{ github.sha }}"
      echo "Actor: ${{ github.actor }}"

  # 印出完整 event payload
  - name: Dump event
    run: cat "$GITHUB_EVENT_PATH" | jq .

  # 檢查檔案系統
  - name: List files
    run: |
      pwd
      ls -la

常見問題

問題 1:Workflow 沒有被觸發

可能原因

  • YAML 檔案不在 .github/workflows/ 目錄下
  • YAML 語法錯誤
  • 分支名稱不符合 branches 過濾條件
  • paths 過濾排除了變更的檔案
  • Workflow 被停用(Repository → Actions → 手動啟用)

排查步驟

# 驗證 YAML 語法
yamllint .github/workflows/ci.yml

# 檢查分支名稱
git branch --show-current

問題 2:Permission denied 錯誤

症狀

Error: Resource not accessible by integration

解決方案

# 明確設定所需權限
permissions:
  contents: read
  pull-requests: write
  issues: write

也可能需要在 Repository → Settings → Actions → General → Workflow permissions 中調整預設權限。


問題 3:快取沒有命中

可能原因

  • key 中使用的 hashFiles() 路徑不正確
  • 不同作業系統的快取不共用(runner.os 不同)
  • 快取超過 10 GB 上限被自動清除

建議

- uses: actions/cache@v4
  with:
    path: node_modules
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |               # 提供 fallback key
      ${{ runner.os }}-node-

問題 4:Job 之間無法共享檔案

原因:每個 Job 在獨立的 Runner 上執行,檔案系統不共享。

解決方案:使用 Artifact 傳遞:

# Job A:上傳
- uses: actions/upload-artifact@v4
  with:
    name: my-data
    path: output/

# Job B:下載
- uses: actions/download-artifact@v4
  with:
    name: my-data

總結

核心概念速查

Workflow(.yml 檔案)
├── Event(觸發條件):push / PR / schedule / dispatch
├── Job(工作單元):在獨立 Runner 上執行
│   ├── Step:uses: Action 或 run: Shell
│   └── Step:支援 if 條件、output 傳遞
├── 可重用元件
│   ├── Reusable Workflow:完整流程重用
│   └── Composite Action:步驟組合重用
└── 安全
    ├── Secrets / Variables:加密 vs 明文
    ├── Permissions:最小權限原則
    └── Environment:部署審核控制

語法速查

語法 用途 範例
on: 觸發條件 on: push:
jobs: 定義 Job jobs: build:
runs-on: 指定 Runner runs-on: ubuntu-latest
uses: 使用 Action uses: actions/checkout@v4
run: Shell 指令 run: npm test
with: Action 參數 with: node-version: '20'
env: 環境變數 env: CI: true
if: 條件執行 if: github.ref == 'refs/heads/main'
needs: Job 依賴 needs: [build, test]
strategy.matrix: 矩陣建構 matrix: node: [18, 20]
secrets: 存取密鑰 ${{ secrets.API_KEY }}
permissions: 權限控制 permissions: contents: read
concurrency: 並行控制 cancel-in-progress: true

常用 Action 速查

Action 用途
actions/checkout@v4 檢出程式碼
actions/setup-node@v4 設定 Node.js
actions/setup-go@v5 設定 Go
actions/setup-java@v4 設定 Java
actions/cache@v4 快取依賴
actions/upload-artifact@v4 上傳建構產出物
actions/download-artifact@v4 下載建構產出物
docker/build-push-action@v5 建構推送 Docker Image
docker/login-action@v3 Docker Registry 登入
actions/github-script@v7 執行 GitHub API 腳本

建立日期:2026-04-10 最後更新:2026-04-10

🔗相關文章