GitHub Actions CI/CD
大白话解释: GitHub Actions 就像"自动化的流水线"。当你推送代码到 GitHub 时,它会自动帮你做一系列事情:安装依赖、运行测试、构建项目、部署到服务器。
为什么要用 CI/CD?
- 自动化:不用手动执行
npm run build、npm run deploy等命令 - 一致性:每次构建环境都一样,不会出现"在我电脑上能跑"的问题
- 快速反馈:代码有问题,马上就能知道,不用等到部署后才发现
- 节省时间:推完代码就不用管了,自动部署到服务器
核心概念:
- Workflow:整个自动化流程(如"部署到生产环境")
- Job:流程中的一个任务(如"构建"、"测试"、"部署")
- Step:任务中的一个步骤(如"安装依赖"、"运行测试")
- Runner:执行任务的服务器(GitHub 提供免费的)
GitHub Actions 是 GitHub 内置的 CI/CD 平台,用于自动化构建、测试和部署。前端项目最常用的自动化工具之一。
核心概念
| 概念 | 说明 |
|---|---|
| Workflow | 一个自动化流程,定义在 .github/workflows/*.yml |
| Event | 触发 Workflow 的事件(push、PR、定时等) |
| Job | Workflow 中的一个任务,包含多个 Step |
| Step | Job 中的一个步骤,执行一条命令或一个 Action |
| Action | 可复用的步骤单元(社区有大量现成的) |
| Runner | 执行 Job 的服务器(GitHub 提供,也可自托管) |
免费额度与计费
| Runner | 免费分钟数 | 存储 | 超出费用 |
|---|---|---|---|
| Ubuntu / Windows | 2000 分钟 | 500 MB | $0.008/分钟 |
| macOS | 250 分钟 | 500 MB | $0.08/分钟 |
| 自托管 Runner | 无限 | 不限 | 免费 |
💡 私有仓库才受免费额度限制,公开仓库不限分钟数。存储指 Artifacts 和日志的总占用量。
基本结构
# .github/workflows/deploy.yml
name: Deploy # Workflow 名称
on: # 触发条件
push:
branches: [main] # push 到 main 分支时触发
jobs: # 任务列表
build-and-deploy: # 任务名称
runs-on: ubuntu-latest # 运行环境
steps: # 步骤列表
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: yarn install
- name: Build
run: yarn build
- name: Deploy
run: echo "Deploying..."前端项目完整示例
Vue/React 项目部署到 GitHub Pages
name: Deploy to GitHub Pages
on:
push:
branches: [main]
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: yarn
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Build
run: yarn build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: dist
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4部署到 Vercel
name: Deploy to Vercel
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: yarn
- name: Install dependencies
run: yarn install
- name: Build
run: yarn build
env:
VITE_API_URL: ${{ secrets.VITE_API_URL }}
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'超时设置
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 30 # 单个 Job 最大运行时间,默认 360 分钟
steps:
- uses: actions/checkout@v4
- run: yarn install
- run: yarn build
deploy:
runs-on: ubuntu-latest
timeout-minutes: 10 # 部署任务设置更短超时
steps:
- run: echo "Deploy"💡
timeout-minutes放在 Job 级别,Step 级别不支持此字段。超时后 Job 被强制取消。
容器服务(services)
services 字段为 Job 启动依赖的容器,常用于集成测试需要数据库、缓存等场景。
jobs:
test:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: testdb
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping -h localhost"
--health-interval=10s
--health-timeout=5s
--health-retries=5
redis:
image: redis:7
ports:
- 6379:6379
options: >-
--health-cmd="redis-cli ping"
--health-interval=10s
--health-timeout=5s
--health-retries=5
steps:
- uses: actions/checkout@v4
- name: Run tests
env:
DB_HOST: localhost
DB_PORT: 3306
DB_USER: root
DB_PASSWORD: root
DB_NAME: testdb
REDIS_HOST: localhost
REDIS_PORT: 6379
run: yarn test💡 services 仅在 ubuntu-latest 等 Linux Runner 上支持,macOS 和 Windows 不支持。容器通过
localhost+ 映射端口访问。
Job 容器运行(container)
让整个 Job 在指定容器内执行,而非直接在 Runner 上运行。
jobs:
build:
runs-on: ubuntu-latest
container:
image: node:20-alpine # Job 的所有步骤都在此容器内执行
options: --user root
steps:
- uses: actions/checkout@v4
- run: node -v
- run: yarn install
- run: yarn build💡 使用
container时,actions/checkout等 Action 会在容器内执行。可指定volumes挂载宿主机目录。
GITHUB_TOKEN 详解
GITHUB_TOKEN 是 GitHub Actions 自动创建的临时令牌,每个 Job 独立生成,工作流结束后失效。
基本用法
steps:
- name: Checkout
uses: actions/checkout@v4
# token 参数默认值就是 github.token,通常无需显式指定
# 仅在需要使用其他 PAT(如跨仓库访问)时才需要设置
- name: Create release
uses: actions/github-script@v7
with:
script: |
// github.rest 是 Octokit REST API 客户端
// 详见:https://octokit.github.io/rest.js
await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: 'v1.0.0'
});权限配置
# 方式1:顶层声明(推荐)
permissions:
contents: read
issues: write
pull-requests: write
# 方式2:只读模式
permissions: read-all
# 方式3:读写模式
permissions: write-all常用权限范围
| 权限 | 说明 |
|---|---|
contents | 读写仓库代码和文件 |
issues | 读写 Issue |
pull-requests | 读写 PR |
packages | 读写 GitHub Packages |
actions | 管理 Actions(取消工作流等) |
deployments | 读写部署状态 |
statuses | 读写提交状态 |
pages | 部署 GitHub Pages |
id-token | 获取 OIDC token(用于云服务联合认证) |
💡 默认权限可在仓库 Settings → Actions → Workflow permissions 中设置为"Read and write permissions"或"Read repository contents and packages permissions"。
路径过滤器(paths / paths-ignore)
根据变更文件路径决定是否触发 Workflow。
on:
push:
branches: [main]
paths:
- 'src/**' # src 目录下任意文件变更触发
- 'package.json'
- '!docs/**' # 排除 docs 目录
pull_request:
branches: [main]
paths-ignore:
- '*.md' # 仅修改 Markdown 文件时不触发
- 'docs/**'
- '.github/FUNDING.yml'分支过滤器通配符
| 通配符 | 说明 | 示例 |
|---|---|---|
* | 匹配单层级任意字符 | release/* 匹配 release/v1,不匹配 release/v1/fix |
** | 匹配多层级任意字符 | release/** 匹配 release/v1 和 release/v1/fix |
! | 排除模式 | !main 排除 main 分支 |
[abc] | 字符集 | [ab] 匹配 a 和 b |
on:
push:
branches:
- 'main'
- 'release/**' # 匹配 release/v1、release/v2.0.1 等
- 'feature/**'
- '!experimental/**' # 排除 experimental 分支workflow_dispatch 输入类型
手动触发支持 5 种输入类型:
| 类型 | 说明 | 特点 |
|---|---|---|
string | 文本输入(默认) | 支持 default |
boolean | 布尔选择 | 值为 true/false |
choice | 下拉菜单 | 必须提供 options 列表 |
environment | 环境选择 | 自动列出仓库已配置的环境 |
number | 数字输入 | 支持 default,值为数字字符串 |
on:
workflow_dispatch:
inputs:
name:
type: string
description: '部署名称'
required: true
default: 'my-app'
dry-run:
type: boolean
description: '是否为模拟运行'
required: false
default: false
environment:
type: choice
description: '部署环境'
required: true
options:
- staging
- production
target-env:
type: environment
description: '选择已配置的环境'
required: true
replicas:
type: number
description: '副本数量'
required: false
default: '1'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Show inputs
run: |
echo "Name: ${{ inputs.name }}"
echo "Dry run: ${{ inputs.dry-run }}"
echo "Environment: ${{ inputs.environment }}"
echo "Target: ${{ inputs.target-env }}"
echo "Replicas: ${{ inputs.replicas }}"环境变量作用域
环境变量可在 3 个级别定义,作用范围逐级缩小:
workflow 级别
env:
NODE_ENV: production
CI: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo $NODE_ENV # productionjob 级别
jobs:
build:
runs-on: ubuntu-latest
env:
BUILD_ENV: staging
steps:
- run: echo $BUILD_ENV # staging
- run: echo $NODE_ENV # production(继承 workflow 级别环境变量)
deploy:
runs-on: ubuntu-latest
steps:
- run: echo $BUILD_ENV # 空(job 之间不共享)step 级别
steps:
- name: Build
run: yarn build
env:
VITE_API_URL: ${{ secrets.API_URL }}
- name: Test
run: yarn test
# 这里 VITE_API_URL 不可用优先级
step 级别 > job 级别 > workflow 级别。同名变量高优先级覆盖低优先级。
常用触发事件
on:
# push 到指定分支
push:
branches: [main, develop]
tags:
- 'v*' # tag 推送(发布版本)
# Pull Request
pull_request:
branches: [main]
# 定时任务(每天 UTC 0 点)
schedule:
- cron: '0 0 * * *'
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production常用 Actions
缓存依赖(加速构建)
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: yarn # 自动缓存 yarn 依赖
# 或手动缓存
- name: Cache dependencies
uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-node-矩阵测试(多版本)
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: yarn install
- run: yarn test发送通知
- name: Send notification
if: failure()
uses: appleboy/telegram-action@master
with:
to: ${{ secrets.TELEGRAM_CHAT_ID }}
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
message: "部署失败:${{ github.repository }}"Secrets 管理
设置 Secrets
操作步骤:
- 进入仓库 → Settings → Secrets and variables → Actions
- 点击 "New repository secret"
- 输入 Secret 名称(如
VERCEL_TOKEN)和对应的值 - 点击 "Add secret" 保存
Secret 类型:
| 类型 | 说明 | 适用场景 |
|---|---|---|
| Repository secrets | 仓库级别,所有 workflow 可用 | 通用密钥(API Token、SSH Key) |
| Environment secrets | 环境级别,需指定 environment 才能访问 | 多环境隔离(生产/测试用不同密钥) |
| Organization secrets | 组织级别,可在多个仓库共享 | 团队共享的通用密钥 |
⚠️ Secret 在日志中自动显示为
***,无法被echo输出。不要在代码中硬编码密钥。
使用 Secrets
steps:
- name: Build
run: yarn build
env:
# 通过 ${{ secrets.SECRET_NAME }} 引用仓库中配置的 Secret
VITE_API_URL: ${{ secrets.VITE_API_URL }} # API 地址
VITE_APP_TITLE: ${{ secrets.VITE_APP_TITLE }} # 应用标题
- name: Deploy
run: echo "Deploying..."
env:
# SSH 部署所需的密钥(在仓库 Settings → Secrets 中添加)
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} # SSH 私钥内容
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }} # 部署令牌多环境部署
name: Deploy
on:
push:
branches: [main, develop]
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: yarn
- name: Install
run: yarn install
- name: Build
run: yarn build
env:
VITE_API_URL: ${{ github.ref == 'refs/heads/main' && secrets.PROD_API_URL || secrets.STAGING_API_URL }}
- name: Deploy
run: |
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
echo "Deploy to production"
else
echo "Deploy to staging"
fi常见问题
Workflow 不触发
# 检查 YAML 语法
# 确认分支名正确
# 确认 Actions 已启用(仓库 Settings → Actions)构建失败
# 添加调试信息
- name: Debug
run: |
node -v
npm -v
yarn -v
ls -la缓存失效
# 使用 lock 文件的 hash 作为缓存 key
key: ${{ runner.os }}-node-${{ hashFiles('yarn.lock') }}YAML 语法速查
常用数据类型
| 类型 | 示例 | 说明 |
|---|---|---|
| 字符串 | 'hello' 或 hello | 单引号可避免特殊字符解析 |
| 多行字符串 | | 或 > | | 保留换行,> 折叠换行 |
| 数字 | 42、3.14 | 整数和浮点数 |
| 布尔 | true、false | 注意不要加引号 |
| 数组 | [a, b, c] 或 - a | 行内和多行两种写法 |
| 对象 | key: value | 键值对 |
多行字符串
# | 保留换行符
run: |
echo "第一行"
echo "第二行"
# > 折叠换行符(合并为一行)
run: >
echo "这会被合并为一行"变量引用
# 上下文表达式
${{ github.ref }}
${{ secrets.MY_SECRET }}
${{ vars.MY_VAR }}
# 环境变量
env:
MY_VAR: hello
steps:
- run: echo $MY_VAR # shell 语法
- run: echo ${{ env.MY_VAR }} # GitHub 表达式语法锚点与别名(复用配置)
# 定义锚点
common-steps: &common-steps
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
# 引用锚点
jobs:
build:
steps:
- *common-steps
- run: yarn build
test:
steps:
- *common-steps
- run: yarn test条件执行
if 条件表达式
steps:
# 仅在 main 分支执行
- name: Deploy
if: github.ref == 'refs/heads/main'
run: echo "Deploy to production"
# 仅在 PR 时执行
- name: Lint
if: github.event_name == 'pull_request'
run: yarn lint
# 多条件组合
- name: Deploy production
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: echo "Deploy"常用条件函数
| 函数 | 说明 | 使用场景 |
|---|---|---|
success() | 所有前置步骤成功(默认) | 可省略 |
failure() | 前置步骤有失败 | 发送失败通知 |
always() | 无论成功失败都执行 | 清理资源、上传日志 |
cancelled() | 工作流被取消时 | 取消通知 |
steps:
- name: Run tests
run: yarn test
- name: Notify on failure
if: failure()
run: echo "Tests failed!"
- name: Cleanup
if: always()
run: echo "Always runs"
- name: On cancel
if: cancelled()
run: echo "Workflow was cancelled"表达式中的运算符
| 运算符 | 示例 | 说明 |
|---|---|---|
== | github.ref == 'refs/heads/main' | 等于 |
!= | github.event_name != 'push' | 不等于 |
&& | a && b | 逻辑与 |
|| | a || b | 逻辑或 |
! | !success() | 逻辑非 |
contains() | contains(github.event.head_commit.message, '[skip ci]') | 字符串包含 |
startsWith() | startsWith(github.ref, 'refs/tags/') | 字符串前缀 |
endsWith() | endsWith(github.ref, '-release') | 字符串后缀 |
Job 间依赖与数据传递
needs 依赖
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "Building..."
test:
needs: build # 等 build 完成后执行
runs-on: ubuntu-latest
steps:
- run: echo "Testing..."
deploy:
needs: [build, test] # 等 build 和 test 都完成后执行
runs-on: ubuntu-latest
steps:
- run: echo "Deploying..."outputs 数据传递
jobs:
build:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get-version.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Get version
id: get-version
run: |
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Show version
run: echo "Deploying version ${{ needs.build.outputs.version }}"Artifacts 上传下载
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: yarn install && yarn build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: dist-files
path: dist/
retention-days: 7 # 保留天数
compression-level: 6 # 压缩级别 0-9
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: dist-files
path: dist/
- name: Deploy
run: ls dist/多 Artifact 并行上传
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
platform: [web, mobile]
steps:
- uses: actions/checkout@v4
- run: yarn build:${{ matrix.platform }}
- name: Upload
uses: actions/upload-artifact@v4
with:
name: build-${{ matrix.platform }}
path: dist/${{ matrix.platform }}
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
path: all-builds
- run: ls all-builds/环境与部署保护规则
environment 配置
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- run: echo "Deploying to production"使用环境变量
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy
run: echo "Deploying to ${{ vars.DEPLOY_URL }}"
env:
API_KEY: ${{ secrets.API_KEY }}部署保护规则
在仓库 Settings → Environments 中配置:
| 保护规则 | 说明 |
|---|---|
| Required reviewers | 指定审批人,部署前需手动批准 |
| Wait timer | 部署前等待指定分钟数 |
| Deployment branches | 限制可部署的分支(如仅 main) |
多环境流水线
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment:
name: staging
url: https://staging.example.com
steps:
- run: echo "Deploy to staging"
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- run: echo "Deploy to production (requires approval)"复合 Action(Composite Action)
复合 Action 将多个步骤打包为一个可复用的 Action。
创建复合 Action
# .github/actions/setup-project/action.yml
name: 'Setup Project'
description: 'Checkout, setup Node.js and install dependencies'
inputs:
node-version:
description: 'Node.js version'
required: false
default: '20'
runs:
using: 'composite'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: yarn
- name: Install dependencies
shell: bash
run: yarn install --frozen-lockfile使用复合 Action
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Setup project
uses: ./.github/actions/setup-project
with:
node-version: '20'
- name: Build
run: yarn build
test:
runs-on: ubuntu-latest
steps:
- name: Setup project
uses: ./.github/actions/setup-project
- name: Test
run: yarn test复合 Action 输出
# .github/actions/get-version/action.yml
name: 'Get Version'
outputs:
version:
description: 'Package version'
value: ${{ steps.get-version.outputs.version }}
runs:
using: 'composite'
steps:
- name: Get version
id: get-version
shell: bash
run: echo "version=$(node -p 'require(\"./package.json\").version')" >> "$GITHUB_OUTPUT"可重用 Workflow(Reusable Workflow)
定义可重用 Workflow
# .github/workflows/reusable-deploy.yml
name: Reusable Deploy
on:
workflow_call: # 声明为可重用
inputs:
environment:
required: true
type: string
node-version:
required: false
type: string
default: '20'
secrets:
DEPLOY_KEY:
required: true
outputs:
deploy-url:
description: 'Deployed URL'
value: ${{ jobs.deploy.outputs.url }}
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
outputs:
url: ${{ steps.deploy.outputs.url }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: yarn
- run: yarn install --frozen-lockfile
- run: yarn build
- name: Deploy
id: deploy
run: |
echo "Deploying to ${{ inputs.environment }}"
echo "url=https://${{ inputs.environment }}.example.com" >> "$GITHUB_OUTPUT"
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}调用可重用 Workflow
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy-staging:
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: staging
node-version: '20'
secrets:
DEPLOY_KEY: ${{ secrets.STAGING_DEPLOY_KEY }}
deploy-production:
needs: deploy-staging
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: production
secrets:
DEPLOY_KEY: ${{ secrets.PROD_DEPLOY_KEY }}调用远程可重用 Workflow
jobs:
call-remote:
uses: org/repo/.github/workflows/shared.yml@main
with:
config: production
secrets: inherit # 传递所有 secrets常用场景模板
PR 自动 Code Review
name: Auto Code Review
on:
pull_request:
types: [opened, synchronize]
permissions:
contents: read
pull-requests: write
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v44
with:
files: |
src/**/*.{ts,tsx,js,jsx}
- name: Run ESLint on changed files
if: steps.changed-files.outputs.any_changed == 'true'
run: |
yarn eslint ${{ steps.changed-files.outputs.all_changed_files }} \
--format json --output-file eslint-report.json
continue-on-error: true
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
let report = 'No ESLint issues found.';
try {
const results = JSON.parse(fs.readFileSync('eslint-report.json', 'utf8'));
const errors = results.reduce((sum, f) => sum + f.errorCount, 0);
const warnings = results.reduce((sum, f) => sum + f.warningCount, 0);
report = `ESLint: ${errors} errors, ${warnings} warnings`;
} catch (e) {}
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `### Code Review\n${report}`
});自动创建 Tag 和 Release
name: Auto Release
on:
push:
branches: [main]
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get version
id: version
run: |
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Check if tag exists
id: check-tag
run: |
if git rev-parse "v${{ steps.version.outputs.version }}" >/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi
- name: Create tag and release
if: steps.check-tag.outputs.exists == 'false'
uses: actions/github-script@v7
with:
script: |
const version = '${{ steps.version.outputs.version }}';
await github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `refs/tags/v${version}`,
sha: context.sha
});
await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: `v${version}`,
name: `v${version}`,
generate_release_notes: true
});构建 Docker 镜像并推送到 Registry
💡 此示例需要项目根目录有 Dockerfile。以下是一个配套的多阶段构建 Dockerfile:
# Dockerfile — 配套的多阶段构建(放在项目根目录)
FROM node:20-alpine AS builder # 构建阶段:使用 Node.js 20 Alpine
WORKDIR /app # 设置工作目录
COPY package.json yarn.lock ./ # 复制依赖文件
RUN yarn install --frozen-lockfile # 安装依赖
COPY . . # 复制源代码
RUN yarn build # 执行构建
FROM nginx:alpine # 运行阶段:使用 nginx
COPY --from=builder /app/dist /usr/share/nginx/html # 复制构建产物
EXPOSE 80 # 声明端口
CMD ["nginx", "-g", "daemon off;"] # 启动 nginxname: Build and Push Docker Image
on:
push:
tags: ['v*'] # 推送 v 开头的 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 # 推送镜像到 GHCR
steps:
- uses: actions/checkout@v4 # 拉取代码
- name: Log in to Container Registry
uses: docker/login-action@v3 # 登录 GitHub Container Registry
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} # 当前触发者用户名
password: ${{ secrets.GITHUB_TOKEN }} # GitHub 自动生成的临时令牌
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5 # 自动提取 tag 信息生成镜像标签
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}} # 如 v1.2.3
type=semver,pattern={{major}}.{{minor}} # 如 1.2
type=sha # Git commit SHA
- name: Build and push
uses: docker/build-push-action@v6 # 构建并推送镜像
with:
context: . # 构建上下文为当前目录
push: true # 构建后自动推送
tags: ${{ steps.meta.outputs.tags }} # 使用 metadata 生成的标签
labels: ${{ steps.meta.outputs.labels }} # 使用 metadata 生成的标签
cache-from: type=gha # 从 GitHub Actions 缓存读取构建层
cache-to: type=gha,mode=max # 将构建层写入 GitHub Actions 缓存Monorepo 按需构建(path filter)
name: Monorepo CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
changes:
runs-on: ubuntu-latest
outputs:
frontend: ${{ steps.filter.outputs.frontend }}
backend: ${{ steps.filter.outputs.backend }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
frontend:
- 'packages/frontend/**'
backend:
- 'packages/backend/**'
build-frontend:
needs: changes
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: yarn
- run: yarn install --frozen-lockfile
- run: yarn workspace frontend build
build-backend:
needs: changes
if: needs.changes.outputs.backend == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: yarn
- run: yarn install --frozen-lockfile
- run: yarn workspace backend build调试技巧
启用调试日志
| 方法 | 设置方式 | 说明 |
|---|---|---|
| Actions Step Debug | Settings → Secrets → ACTIONS_STEP_DEBUG = true | 显示每个 Step 的详细日志 |
| Actions Runner Debug | Settings → Secrets → ACTIONS_RUNNER_DEBUG = true | 显示 Runner 级别日志 |
也可以在触发 Workflow 时通过 URL 参数启用:
https://github.com/org/repo/actions/runs/123456?debug=trueact 本地测试工具
act 可在本地运行 GitHub Actions Workflow,无需推送到远程仓库。
# 安装(macOS)
brew install act
# 安装(Windows)
winget install nektos.act
# 运行默认事件(push)
act
# 指定事件类型
act pull_request
# 指定 Job
act -j build
# 使用中型镜像(包含更多工具)
act -P ubuntu-latest=catthehacker/ubuntu:act-latest
# 模拟 secrets
act -s MY_SECRET=value
# 列出所有 Workflow 和 Job
act -l
# 详细输出
act -v调试 Workflow 语法
# 输出上下文信息(排查变量问题)
- name: Debug context
run: |
echo "Event: ${{ github.event_name }}"
echo "Ref: ${{ github.ref }}"
echo "SHA: ${{ github.sha }}"
echo "Actor: ${{ github.actor }}"
echo "Runner OS: ${{ runner.os }}"
echo "Workspace: ${{ github.workspace }}"
# 输出完整事件 payload
- name: Dump event
run: cat ${{ github.event_path }}常见调试场景
| 问题 | 排查方法 |
|---|---|
| 环境变量为空 | echo 输出变量值,检查 Secrets 配置 |
| 条件不满足 | 查看 Step 是否被跳过(灰色) |
| 权限不足 | 检查 permissions 配置和 Token 权限 |
| 缓存未命中 | 查看 Cache 命中日志,对比 key |
| YAML 语法错误 | 使用 actionlint 本地检查 |