摘要 :本文以一条可直接复制运行的 GitHub Actions 流水线为主线,手把手教你把「代码提交 → 自动构建 → 多版本测试 → 产物发布」串成端到端的 CI/CD 自动化流程。内容覆盖 workflow 核心概念、依赖缓存、matrix 矩阵并行测试、Artifacts 跨 Job 产物传递,以及 npm 包与 Docker 镜像的自动发布,并标注每个关键技术点的一手官方文档来源与权限(permissions)配置。所有 action 均固定到稳定大版本,复制进
.github/workflows/即可跑通。
导语
每次 push 之后,还要手动跑 npm install、build、test,再敲一串命令发版------这是很多前端/Node 同学每天的重复劳动。更糟的是,测试只在自己机器上跑过,合并到 CI 却因为环境不一致翻车,排查半天发现是别人机器上的 Node 版本和你不一样。
CI/CD(持续集成 / 持续交付,Continuous Integration / Continuous Delivery)就是把这些重复的「构建、测试、发布」动作交给机器自动完成:你只管推代码,流水线在后台帮你验证、打包、发布。GitHub Actions 是 GitHub 原生提供的事件驱动型 CI/CD 工作流引擎,和仓库深度集成,配置即代码(一个 YAML 文件搞定)。
本文的目标,是带你从 0 到 1 搭出一条能真正上线的流水线,而不是只贴几个零散片段。关于缓存加速与矩阵测试的更多中文实战拆解,也可以参考这篇 CSDN 文章:GitHub Actions 自动化运维实战:从零到一构建高效 CI/CD 流水线。
声明:本文基于个人使用体验,非商业推广。

一、为什么需要 CI/CD:把重复的构建/测试/发布交给机器
先说清楚痛点在哪。手工交付的典型循环是:推代码 → 等自己或别人部署 → 出 bug → 回滚 → 再推。这个过程耗时长、易遗漏,而且「能跑在我机器上」不等于「能在 CI 上跑」。
自动化之后,每次 push 都会自动触发测试与构建,问题在合并前就被拦截;发布也从「手动敲命令」变成「打一个 Release 就自动出包」。下面的对比表能直观看出差异:
| 维度 | 手工交付 | GitHub Actions 自动化 |
|---|---|---|
| 触发方式 | 人工记得跑命令 | push / PR / Release 事件自动触发 |
| 环境一致性 | 依赖本机环境,易漂移 | Runner 固定镜像,结果可复现 |
| 多版本测试 | 手动切 Node 版本逐一跑 | matrix 一次并行跑 18/20/22 |
| 发布 | 手敲 npm publish,易忘 token | Release 事件自动发版 |
| 失败回滚 | 靠人反应 | 流水线失败即阻断合并 |
GitHub Actions 的核心抽象是 Workflow → Job → Step 三层结构:Workflow 是一份 YAML 流程定义;Job 是互相独立(或依赖)的任务,运行在 Runner (执行机)上;Step 是 Job 里的一条条具体动作。它们的关系用一张文字流程图表示:
text
Event(例如 push / pull_request)
│
▼
Workflow(.github/workflows/*.yml)
│
├─ Job A runs-on: ubuntu-latest
│ ├─ Step 1: uses actions/checkout@v4 # 拉代码
│ └─ Step 2: run npm ci # 装依赖
│
└─ Job B needs: Job A # 等 A 完成
└─ Step: run npm test # 跑测试
▲ 由 Runner(执行机) 真正执行

本文最终要落地的流水线就是:push 触发 → checkout → 带缓存装依赖 → build → 多版本 test → 发布 npm/Docker。
二、30 秒看懂核心概念与目录约定
动手前,先记住几条约定,能省掉后面 80% 的报错。
Workflow 文件必须放在仓库的 .github/workflows/ 目录下,扩展名是 .yml 或 .yaml。一个最小的 Workflow 顶层包含这些键:name(工作流名)、on(触发事件:push、pull_request、workflow_dispatch 等)、jobs(任务映射)、env(全局环境变量)、defaults(默认 run 配置)。
Job 级别的关键键有:runs-on(运行器标签)、steps(步骤序列)、needs(依赖其他 Job)、strategy(矩阵)、env、secrets。Step 级别则是:uses(引用一个 action)、run(执行 shell 命令)、with(给 action 传参)、name、env。
Runner 就是执行机,分两类:GitHub 托管运行器(ubuntu-latest / windows-latest / macos-latest)开箱即用;自托管运行器则用标签 [self-hosted, linux, x64] 声明。下面这张速查表把几个核心概念并排对照:
| 概念 | 是什么 | 典型写法 |
|---|---|---|
| Workflow | 一份流程 YAML | .github/workflows/ci.yml |
| Job | 一个任务,跑在一台 Runner 上 | build: / test: |
| Step | Job 里的一步 | uses: 或 run: |
| Runner | 真正执行命令的机器 | runs-on: ubuntu-latest |
| Action | 可复用的封装单元 | actions/checkout@v4 |
| Event | 触发工作流的事情 | on: [push, pull_request] |
先看一个最小 Hello World,建立整体结构感:
yaml
# .github/workflows/hello.yml ------ 最小可用结构示意
name: Hello
on: [push] # push 事件触发
jobs:
greet:
runs-on: ubuntu-latest # 用 GitHub 托管的 Ubuntu Runner
steps:
- name: Say hello
run: echo "Hello from GitHub Actions" # 直接执行 shell
三、实战一:给 Node.js 项目搭一条最小可用 CI 流水线
理解了结构,先来一条「能跑起来」的最小 CI:代码拉取 → 装依赖 → 构建 → 测试。绝大多数 workflow 的第一步都是 actions/checkout@v4,它负责把仓库代码拉到 Runner 上。
装 Node 用 actions/setup-node@v4,有一个很实用的参数 cache: 'npm',它能一键启用 npm 依赖缓存,等价于你手动写 actions/cache,但代码更少。关键点是:CI 里要用 npm ci 而不是 npm install------npm ci 严格依据 package-lock.json 安装,保证 CI 与本地依赖树一致、更快、更可复现。
下面这条 workflow 是完整可运行的,直接复制进 .github/workflows/ci.yml 即可:
yaml
# .github/workflows/ci.yml ------ Node.js 最小 CI:checkout → 缓存 → build → test
name: Node.js CI
on:
push:
branches: [main] # 只在 main 分支的 push 上触发
pull_request:
branches: [main] # PR 也触发,合并前拦截问题
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: 拉取代码
uses: actions/checkout@v4 # 固定到稳定大版本 v4
- name: 安装 Node 并开启 npm 缓存
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm' # 自动缓存 ~/.npm,命中即跳过下载
- name: 安装依赖(锁定 lock 文件,可复现)
run: npm ci
- name: 构建
run: npm run build
- name: 运行测试
run: npm test

缓存的命中逻辑是:key 由 runner.os 加 hashFiles('**/package-lock.json') 生成,只有依赖文件变化时才重建缓存;命中时 actions/cache 会输出 cache-hit=true。这正是 setup-node 的 cache: 'npm' 在背后做的事,你不用手写 restore-keys 回退。
四、实战二:多 Job 流水线 + 矩阵构建(并行多版本测试)
一条「能跑」的 CI 还不够。真实项目往往要验证 Node 18 / 20 / 22 三个版本的兼容性------总不能手动切版本跑三遍。strategy.matrix 就是为此设计的:你声明一组变量数组,GitHub Actions 会自动把它们展开成多个并行 Job。
用 needs 可以串联 Job:让 build 先跑,test 等它完成再跑;前序失败,后续不执行。矩阵里每个版本通过 ${``{ matrix.node-version }} 在 step 中引用。如果希望「一个版本挂了其他继续跑」,把 fail-fast 设为 false 即可;max-parallel 则限制并发数,避免一次性开太多机器。
yaml
# .github/workflows/matrix.yml ------ 多 Job + 矩阵:build 一次,test 并行多版本
name: Matrix CI
on: [push, pull_request]
jobs:
build:
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
# 把构建产物留给后面的 test / deploy(下一节用 Artifact 传递)
test:
needs: build # 必须等 build 成功
runs-on: ubuntu-latest
strategy:
fail-fast: false # 某个版本失败,其余继续跑
max-parallel: 3 # 最多并行 3 个 Job
matrix:
node-version: [18, 20, 22] # 三个 Node 版本自动展开
steps:
- uses: actions/checkout@v4
- name: 安装 Node ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm test

上面的配置会展开成 3 版本 × 1 OS = 3 个并行 test Job,而 build 只跑一次。这就是流水线「构建一次、测试复用」的基本思路,也为下一节的产物传递埋下伏笔。
五、实战三:用 Artifacts 在 Job 之间传递产物
build 打出来的 dist/ 目录,怎么安全交给 deploy Job?答案是 Artifacts(构件) :actions/upload-artifact@v4 上传文件,actions/download-artifact@v4 在下游 Job 下载。注意两点:下载方必须用 needs 建立依赖,且 name 要和上传时一致。
一个容易踩的坑:v4 起 Artifact 不可变 ,同一 workflow 内复用要换不同 name(比如 dist-v1)。Artifacts 和缓存(cache)职责不同,下面这张表说清区别:
| 对比项 | 缓存 actions/cache | 构件 Artifacts |
|---|---|---|
| 主要用途 | 加速依赖安装 | 跨 Job / 运行留存构建产物 |
| 命中范围 | 同分支、同 key 命中 | 整个 workflow 内可下载 |
| 是否可下载 | 否(仅 Runner 内) | 是(在 Actions UI 下载) |
| 典型场景 | 缓存 node_modules | 传递 dist/、测试报告 |
yaml
# .github/workflows/artifact.yml ------ build 上传 dist,deploy 下载后部署
name: Artifact Demo
on: [push]
jobs:
build:
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
- name: 上传构建产物
uses: actions/upload-artifact@v4
with:
name: dist # 上传名
path: dist/ # 要传递的目录
retention-days: 7 # 自定义保留天数(不超组织上限)
deploy:
needs: build # 依赖 build,确保产物已上传
runs-on: ubuntu-latest
steps:
- name: 下载构建产物
uses: actions/download-artifact@v4
with:
name: dist # 必须与上传 name 一致
path: dist/
- name: 部署(示例:列出产物)
run: ls -la dist/

六、实战四:自动发布------npm 包 / Docker 镜像
构建测试都通过后,发布也该自动化。发布要「只在打 Release 时触发」,避免误发,所以用 on: release: types: [published]。
发布 npm 包的关键三步:setup-node 设 registry-url,把仓库 Secret NPM_TOKEN 注入环境变量 NODE_AUTH_TOKEN,再 npm publish。注意变量名必须是 NODE_AUTH_TOKEN,npm 客户端会读取它做鉴权。
yaml
# .github/workflows/publish-npm.yml ------ 发布到 npm(仅 Release 触发)
name: Publish to npm
on:
release:
types: [published] # 只在发布 Release 时发版
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: https://registry.npmjs.org
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # 仓库 Secret,绝不写进 YAML
如果要推 Docker 镜像到 GitHub 容器 registry(ghcr.io ),用 docker/login-action 登录,username 填 ${``{ github.actor }},password 用内置的 ${``{ secrets.GITHUB_TOKEN }};再借助 docker/build-push-action 设 push: true。这里务必在 job 级声明 permissions: packages: write,否则默认令牌无权推送。
yaml
# .github/workflows/publish-docker.yml ------ 推送镜像到 ghcr.io
name: Publish Docker
on:
release:
types: [published]
jobs:
docker:
runs-on: ubuntu-latest
permissions:
packages: write # 推送 ghcr 必需
contents: read
steps:
- uses: actions/checkout@v4
- name: 登录 ghcr.io
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: 构建并推送镜像
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}

七、Secrets 与权限:安全管理 Token
敏感信息一律放进 Repository Secrets (路径:Settings → Secrets and variables → Actions),在 YAML 里只通过 ${``{ secrets.NAME }} 引用,绝不以明文写出。GITHUB_TOKEN 是每次 Job 自动注入的内置 Secret,作用域仅限于当前仓库------它很方便,但默认权限可能过宽,发布场景要像上一节那样显式提升 packages 权限。
一个官方文档明确警告的坑:缓存路径里不要存 token / 密码 。任何能发起 PR 的人,都可能通过缓存的读取机制拿到其中的内容。更安全的做法是优先用 OIDC (id-token: write)做云厂商的免密钥登录,避免长期凭证泄露。
| 场景 | 推荐做法 |
|---|---|
| npm token | 存 Secret,注入 NODE_AUTH_TOKEN |
| 云厂商登录 | 用 OIDC 临时凭证,而非长期 AK/SK |
| 跨 Job 传文件 | Artifacts,不塞进缓存 |
| 默认令牌 | job 级显式声明最小 permissions |

八、最佳实践与避坑指南
把前面几节串起来后,再补几条能少踩坑的经验。
第一,固定 action 大版本 :写 actions/checkout@v4 而非 @main 或 @latest,防止上游更新让你的流水线突然失败。第二,托管 Runner 适合公共项目免费使用,重度 / 私有构建可上自托管省成本。第三,控制成本与时长:开缓存、fail-fast、用 concurrency 取消过期的重复运行、给 Job 设 timeout-minutes。
常见翻车点 Top 清单:
- Secrets 没配置 → 发布直接 403;
permissions缺失 →GITHUB_TOKEN无权推 ghcr;- Node 版本漂移 → 本地 20、CI 18,行为不一致;
- Windows / Linux 路径与换行差异 → 脚本在 macOS 跑得好好的,到 Windows Runner 报错;
npm install代替npm ci→ 依赖树不锁定,偶发诡异 bug;- Artifact
name上下不一致 → 下载为空; - 缓存里误存 token → 潜在泄露;
- 忘了
needs→ 下游 Job 在产物还没上传时就启动。
yaml
# 超时与并发控制示例(避免堆积与无限运行)
concurrency:
group: ci-${{ github.ref }} # 同分支同一时间只跑一个
cancel-in-progress: true # 新 push 取消旧运行
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 15 # 单 Job 超时保护
九、总结:一条流水线打通构建、测试与发布
回顾全链路:push → checkout → 缓存依赖 → build → 矩阵 test → Artifact 传递 → 发布 npm / Docker。本文给出的每一段 YAML 都固定了稳定大版本,复制进 .github/workflows/ 就能直接跑通,跑完即具备上线能力。
需要强调的是,CI/CD 不是「配一次就完事」,它是随项目演进持续打磨的。下一步可以探索的方向有:用 Environments 做发布审批门禁、把重复逻辑抽成 Composite Action 复用、用 Dependabot 自动升级 action 与依赖版本以保障安全。把机械的重复交给机器,你只需专注写出更好的代码。

参考资料
- GitHub Docs - Workflow syntax for GitHub Actions:https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
- GitHub Docs - Store and share data with workflow artifacts:https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts
- GitHub Docs - Running variations of jobs(matrix strategy)
- GitHub Docs - Dependency caching reference(actions/cache)
- GitHub Docs - Publishing Node.js packages / Publishing Docker images(GitHub Container Registry)
- CSDN - GitHub Actions 自动化运维实战(中文补充阅读,见正文导语内链)
© 2024 | 转载请注明出处
结论:PASS