Skip to content

GitHub Actions 使用教程:从自动测试到镜像发布

GitHub Actions 根据仓库事件运行自动化工作流。代码提交可以触发测试,标签可以触发制品发布,定时计划可以运行维护脚本,人工按钮可以执行受控任务。最短实践路径是在仓库中创建 .github/workflows/ci.yml,让 Pull Request 和默认分支的提交执行项目已有的安装、测试与构建命令。

本文依据 2026-09-17 核对的 GitHub 官方文档和官方 Action Release 编写。示例完成了结构、YAML 与权限静态检查,没有创建远程测试仓库,也没有消耗 Actions 用量、推送镜像或操作生产环境。因此验证等级为 documented,线上事件、组织策略、账单与部署结果仍需在目标仓库验收。

文中的 Action 主版本面向 GitHub.com 和 GitHub Enterprise Cloud。GitHub Enterprise Server 的内置服务和可用 Action 版本跟随实例版本,不能直接照搬本文的 upload-artifact@v7;GHES 管理员与使用者应以对应实例版本文档为准。

核心模型

一条工作流可以拆成五层:

text
事件 event
  -> 工作流 workflow
    -> 作业 job
      -> 运行器 runner
        -> 步骤 step
  • Eventpushpull_requestworkflow_dispatchschedule、Release 等触发条件。
  • Workflow:位于 .github/workflows/ 的 YAML 文件,一个仓库可以有多个。
  • Job:一组在同一 Runner 上执行的步骤。多个 Job 默认并行,needs 可以建立依赖。
  • Runner:执行 Job 的机器。GitHub 提供托管 Runner,也支持自托管 Runner。
  • Step:Job 内顺序执行的命令或 Action。run 执行 Shell 命令,uses 调用可复用 Action。

GitHub Actions 不会自动修复项目构建。CI 应调用本地已经稳定的测试、检查和构建命令;本地命令没有确定退出码时,工作流也无法可靠判断成败。

准备仓库

开始前确认:

  • 能向目标仓库提交分支和 Pull Request。
  • 项目在本地可以通过锁文件完成依赖安装。
  • 测试和构建命令失败时返回非零退出码。
  • 仓库没有把令牌、密码、私钥或生产配置写进源码。
  • 组织或仓库策略允许使用 GitHub Actions 和所选 Action。
  • 自托管 Runner 已升级到 v2.327.1 或更高版本。本文使用的当前 Action 主版本采用 Node.js 24 Action Runtime;GitHub 托管 Runner 由 GitHub 维护,自托管 Runner 的版本、补丁和运行环境由使用者负责。

示例使用 Node.js 项目,要求仓库已有 package.jsonpackage-lock.json,并定义 npm testnpm run build。其他语言保留相同结构,替换运行环境与命令即可。

创建第一个 CI

在仓库中新建 .github/workflows/ci.yml

yaml
name: CI

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

permissions:
  contents: read

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

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - name: Checkout repository
        uses: actions/checkout@v7

      - name: Set up Node.js
        uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Build
        run: npm run build

提交分支并创建 Pull Request 后,打开仓库 Actions 页面或 Pull Request 的 Checks 区域。成功信号包括:

  1. 名为 CI 的工作流被触发。
  2. test Job 使用预期提交运行。
  3. 安装、测试和构建步骤全部显示成功。
  4. 失败的测试会让 Job 失败,而不是被脚本吞掉。
  5. main 合并后,push 事件再次运行同一套检查。

如果默认分支不是 main,应把 branches 改成真实分支名。workflow_dispatch 文件只有进入默认分支后,才会稳定出现在手工运行入口中。

阅读工作流 YAML

nameon

name 是 Actions 页面中的工作流名称。on 决定触发条件,可以同时声明代码事件、人工触发和定时计划。

yaml
on:
  pull_request:
    paths:
      - "src/**"
      - "package.json"
      - "package-lock.json"
  push:
    tags:
      - "v*"

路径过滤能减少无关运行,但也可能让分支保护等待一个没有被触发的必需检查。把工作流设为必需检查前,应验证文档-only PR、重命名和删除文件等分支。

jobsneeds

每个 Job 通常获得独立 Runner,不自动继承其他 Job 的工作目录。下面的 build 只在 test 成功后执行:

yaml
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: npm test

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - run: npm run build

真实项目中,两个 Job 都需要自行 checkout 和安装依赖,或者由上游上传构建产物供下游下载。不要假设文件会跨 Runner 保留。

runuses

run 适合项目自有命令:

yaml
- name: Lint
  run: npm run lint

uses 会执行另一个仓库或本地目录提供的 Action:

yaml
- name: Checkout repository
  uses: actions/checkout@v7

第三方 Action 本质上是 Runner 上执行的代码。普通教程使用稳定主版本便于阅读;高风险生产工作流应审查源码并固定完整提交 SHA,再通过 Dependabot 跟踪升级。

常用触发方式

目标事件主要边界
Pull Request 检查pull_requestFork PR 通常拿不到仓库 Secret
默认分支持续集成push配合 branchestagspaths 控制范围
人工补跑或维护workflow_dispatch可以定义输入;入口通常要求工作流已在默认分支
定时任务schedule使用 POSIX Cron,在默认分支运行,繁忙时可能延迟
发布流程release 或 tag push应限制写权限、Environment 和制品来源
外部系统触发repository_dispatch调用方认证、事件数据和重放需要额外治理

定时任务不适合秒级调度、严格结算或必须准点执行的生产任务。可以避开整点,并保留 workflow_dispatch 作为补跑入口:

yaml
on:
  schedule:
    - cron: "17 */6 * * *"
      timezone: "Asia/Shanghai"
  workflow_dispatch:

复制旧教程时要特别检查时区支持和默认分支限制。当前配置应以 GitHub 的事件文档为准。

矩阵与缓存

矩阵适合验证多个运行环境:

yaml
strategy:
  fail-fast: false
  matrix:
    node-version: [22, 24]

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

缓存用于复用依赖下载,不是构建制品,也不能代替 npm ci。缓存键应包含锁文件哈希和影响依赖兼容性的环境信息。来自不可信分支的缓存还涉及污染风险,高权限发布 Job 不应盲目信任低权限上下文生成的缓存。

保存构建产物

Artifact 用来保留日志、测试报告、覆盖率报告或构建结果,也可以在 Job 之间传递文件:

yaml
- name: Build
  run: npm run build

- name: Upload build artifact
  uses: actions/upload-artifact@v7
  with:
    name: web-dist
    path: dist/
    if-no-files-found: error
    retention-days: 7

成功后在工作流运行详情页应能看到 web-dist。上传前确认 dist/ 只包含计划共享的文件,不包含 .env、私钥、调试转储或包管理器凭据。

actions/upload-artifact@v4+ 当前不支持 GitHub Enterprise Server,本文的 @v7 示例只用于 GitHub.com 和 GitHub Enterprise Cloud。GHES 应按照实例版本支持矩阵选择 Artifact Action,不能仅把版本号降级后假定其余权限和保留策略完全相同。

Cache 与 Artifact 的区别:

能力CacheArtifact
主要目的加快后续运行保存和传递本次运行结果
典型内容包管理器下载缓存构建目录、报告、日志
是否代表制品可以,但仍需校验来源与完整性
常见使用者后续工作流运行人员或下游 Job

使用 GITHUB_TOKEN

GitHub 为每个 Job 提供临时 GITHUB_TOKEN。权限受仓库、组织、事件来源和工作流 permissions 共同约束。只读 CI 可以采用:

yaml
permissions:
  contents: read

需要写入 GHCR 时再给对应 Job 增加 packages: write。显式设置部分权限后,未列出的权限会变成 none;遇到 403 时应检查实际 API 所需权限,不要直接改成 write-all

工作流可以通过 ${{ secrets.GITHUB_TOKEN }} 使用临时令牌,部分官方示例也使用 ${{ github.token }}。不要把令牌拼进远程 URL、命令参数或日志。GitHub 会遮罩部分已知 Secret,但遮罩不能覆盖所有编码、拆分和外部返回值。

管理 Secrets 与环境

Secret 可以配置在仓库、Environment 或组织层级。普通参数优先使用 Variables;只有敏感值才进入 Secrets。需要部署时,可以建立 stagingproduction Environment,并配置允许分支、审批者、等待时间和环境级 Secret。

Fork Pull Request 和 Dependabot Pull Request 通常无法读取普通仓库 Secret,写权限也会收紧。PR 阶段应执行不需要秘密的构建和测试;发布 Job 放在受保护的默认分支、tag、Release 或人工触发流程中。

支持 OpenID Connect 的云平台应优先使用 id-token: write 换取短期凭据。云端信任策略仍要限制仓库、分支、环境、受众和可执行操作,不能只因为凭据是短期的就放宽权限。

发布镜像到 GHCR

下面的工作流在推送 v* tag 时构建镜像并发布到 GitHub Container Registry。仓库需要包含可构建的 Dockerfile

代码块使用当前官方 Action 的稳定主版本,便于展示完整链路。生产发布应把每个 uses 替换成经过审阅的完整提交 SHA,并让 Dependabot 提交后续升级,而不是长期信任可移动标签。

yaml
name: Publish container

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

permissions:
  contents: read
  packages: write

jobs:
  publish:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v7

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

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

      - name: Generate image metadata
        id: meta
        uses: docker/metadata-action@v6
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=ref,event=tag
            type=sha

      - name: Build and push
        uses: docker/build-push-action@v7
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

创建 tag 前先在普通 CI 中验证 Dockerfile 构建。发布后检查:

  • 工作流使用预期 tag 和提交。
  • Package 页面出现 tag 与提交派生标签。
  • 镜像可见性符合预期。
  • 使用镜像摘要拉取后能启动并通过健康检查。
  • 失败发布不会覆盖已知正常版本,回滚时可以重新部署旧摘要。

若组织策略禁止创建 Package、默认 GITHUB_TOKEN 权限不足或仓库与 Package 关联异常,登录成功也不代表推送一定成功。按官方镜像发布文档核对包权限与归属。

安全基线

不执行不可信 PR 代码

pull_request_target 在基础仓库上下文运行,可能获得写权限与 Secret。不要在该事件中 checkout PR head、安装 PR 修改的依赖或执行 PR 脚本。需要评论、标签等维护操作时,把权限和步骤限制在 GitHub API 操作内。

控制第三方 Action

  • 优先选择 GitHub、语言工具链或目标平台维护的官方 Action。
  • 检查发布者、仓库、维护状态、许可证、权限需求与安全公告。
  • 高风险工作流固定完整提交 SHA。
  • 开启 Dependabot 的 github-actions 更新。
  • 不使用来源不明的 @main、Fork 或复制粘贴脚本。

隔离自托管 Runner

自托管 Runner 能访问更多网络、文件和凭据,也需要自行承担补丁、隔离、容量、清理与审计。不要让不可信 Fork PR 在能访问内网或生产凭据的持久化 Runner 上执行。必须使用时,优先采用短生命周期、任务后销毁的隔离 Runner。

计费与用量

公开仓库、私有仓库、标准托管 Runner、大型 Runner、Artifact 存储和缓存有不同计费与额度规则。固定分钟数、倍率和免费额度会变化,不能从旧文章推断当前账单。

落地前检查仓库或组织的 Actions 用量、预算和告警。自托管 Runner 不产生同一种托管执行分钟费用,但机器、网络、维护、安全和故障成本由使用者承担。工作流成功不代表部署目标、云服务或外部 API 不产生费用。

接入真实项目

推荐按以下顺序扩展:

  1. 让 CI 调用与本地一致的安装、检查、测试和构建脚本。
  2. 在 Pull Request 中使用只读权限,不接入部署凭据。
  3. 将稳定 Job 设置为分支保护的必需检查,避免随意改名。
  4. 保存可追溯 Artifact 或镜像,同一提交只构建一次。
  5. 通过 Environment 和短期凭据接入测试环境。
  6. 为生产环境增加审批、并发控制、超时、监控和回滚。
  7. 多仓库重复后,再提取 composite Action 或 reusable workflow。

复杂逻辑应进入仓库脚本,让开发者可以在本地运行和测试。YAML 负责事件、权限、Runner、Job 依赖和参数编排,不应承载难以调试的大段业务脚本。

故障排查

现象核对顺序
工作流没有触发文件路径、提交分支、YAML、事件过滤、默认分支、仓库/组织策略
YAML 无法解析缩进、列表符号、冒号、引号、表达式位置和编辑器 Schema
npm ci 失败锁文件、Node 版本、registry、原生依赖和工作目录
API 返回 403Job 的实际 permissions、事件来源和组织策略
Secret 是空值Secret 层级、Environment、Fork/Dependabot 和 reusable workflow 传递
Cache 持续 misskey、锁文件路径、操作系统、分支范围和首次运行
Artifact 不存在构建输出路径、工作目录和 if-no-files-found
定时任务延迟默认分支、Cron、仓库活跃状态和平台拥堵
本地成功但 CI 失败大小写、未提交文件、工具版本、Shell、时区、行尾和外部依赖
镜像登录成功但推送失败packages: write、Package 归属、组织策略和镜像名称

定位问题时先找到第一个失败步骤。不要用 continue-on-error: true、无限重试或扩大全部权限掩盖根因。

上线检查

  • [ ] Workflow 位于 .github/workflows/
  • [ ] 本地测试、构建和退出码稳定。
  • [ ] 触发分支、tag、路径与默认分支正确。
  • [ ] Job 有合理超时和并发策略。
  • [ ] 使用锁文件和可重复安装命令。
  • [ ] permissions 只开放当前 Job 所需范围。
  • [ ] Fork PR 不执行带 Secret 的发布步骤。
  • [ ] pull_request_target 不执行 PR 代码。
  • [ ] 第三方 Action 已审查;高风险流程固定提交 SHA。
  • [ ] 日志、Artifact 和缓存不包含凭据。
  • [ ] 发布使用 Environment、短期凭据或等价保护。
  • [ ] 失败通知、回滚版本和业务验收明确。
  • [ ] 已检查 Actions 用量、存储和外部服务费用。

参考资料

全部公开文章由同一个站点构建和发布。