代码写完推送到仓库,测试要本地跑一遍,服务器要手动登录上去拉代码、重启服务,这套流程相信不少开发者都经历过。一旦团队成员变多、发布频率变高,手动操作的出错概率也会直线上升。GitHub Actions可以直接在仓库里配置自动化流水线,push代码时自动跑测试,测试通过后自动构建并部署,整个过程不需要人工介入。本文以一个典型的Node.js项目为例,从零搭建一条完整的CI/CD流水线。

一、认识GitHub Actions的核心概念
在动手写配置之前,需要先弄清楚几个基本概念。GitHub Actions的核心是工作流(Workflow),它是一个存放在仓库.github/workflows目录下的YAML文件,一个仓库可以同时存在多个工作流文件,互不干扰。工作流由一个或多个任务(Job)组成,每个任务运行在一台独立的虚拟机上,任务之间默认并行执行,也可以通过依赖声明让它们按顺序执行。
每个任务内部又由若干步骤(Step)构成,步骤可以是执行一条shell命令,也可以直接引用社区共享的动作(Action)。比如官方提供的actions/checkout负责拉取代码,actions/setup-node负责安装指定版本的Node.js环境。这些现成的动作可以直接复用,省去大量环境配置工作。
触发方式也很灵活,最常见的是on: push,即代码推送到指定分支时触发;也可以配置on: pull_request在提交PR时触发,on: workflow_dispatch支持手动点击按钮触发,on: schedule则可以定时执行。一个成熟的项目通常会组合使用多种触发条件。
二、编写自动化测试工作流
先从最基础的测试环节做起。在项目根目录创建.github/workflows/ci.yml文件,内容如下:
name: Node.js CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x, 18.x, 20.x]
steps:
- name: 拉取代码
uses: actions/checkout@v4
- name: 安装 Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: 安装依赖
run: npm ci
- name: 执行代码检查
run: npm run lint
- name: 执行单元测试
run: npm test
这份配置里有几个值得注意的点。首先是strategy.matrix,它声明了一个Node版本矩阵,GitHub会分别为16.x、18.x、20.x三个版本各启动一个独立的虚拟机跑测试,能提前发现某个版本下的兼容性问题。如果测试资源紧张,可以只保留项目实际使用的版本。
其次是cache: 'npm',这个配置会自动缓存npm的依赖包,下次运行时直接恢复缓存而不是重新下载,通常能把依赖安装时间缩短一半以上。npm ci命令比npm install更适合CI环境,它会严格按照package-lock.json安装,保证每次构建的依赖版本完全一致,装完还会自动删除node_modules外的残留文件,环境更干净。
如果测试代码已经配置了覆盖率统计工具(比如Jest),还可以把覆盖率报告产物上传,方便后续查看:
- name: 上传测试覆盖率报告
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
三、自动构建与部署到服务器
测试通过后,下一步就是部署。部署方式取决于项目类型:前端项目构建出的静态文件可以发布到GitHub Pages或对象存储;后端服务一般通过SSH登录服务器拉取代码并重启进程。先看一个构建并部署前端静态站点的例子:
build-and-deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
cache: 'npm'
- name: 安装依赖并构建
run: |
npm ci
npm run build
- name: 部署到GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist
这里的needs: test声明了任务依赖,表示必须等test任务全部成功后才执行构建部署,这就把测试和部署串成了完整流水线。如果测试失败,部署不会发生,线上环境自然不会受到影响。
部署到自有服务器则需要借助SSH。社区提供的appleboy/ssh-action封装得比较完善,配合仓库的Secrets功能可以安全地传递密钥。先在GitHub仓库的Settings页面选择Secrets and variables,再进入Actions,添加SSH_HOST、SSH_USER、SSH_KEY三个加密变量,然后编写部署任务:
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- name: 通过SSH部署到服务器
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /var/www/myapp
git pull origin main
npm ci --production
pm2 reload app.js
这种模式本质上是让GitHub的虚拟机代替人工登录服务器执行命令。脚本里的内容就是平时手动部署要做的事:进入项目目录、拉取最新代码、安装生产依赖、用pm2重启服务。密码、私钥这类敏感信息一律放进Secrets,Actions运行时会自动解密注入,日志中也会自动打码,千万不要把密钥明文写在YAML文件里提交到仓库。
四、常见问题与优化建议
流水线搭建完成后,运行中难免遇到一些问题。最常见的是依赖安装失败,多半是package-lock.json和package.json不同步导致的,本地执行一次npm install重新提交锁文件即可。另一个高频问题是脚本没有执行权限,可以在部署脚本前加一句chmod +x deploy.sh解决。
性能方面,除了开启依赖缓存,还可以从几个方向优化:合并重复的步骤减少虚拟机启动开销;把lint和test放在同一个任务里并行执行shell命令;对于monorepo项目,利用paths过滤条件,只有相关目录的文件变更时才触发对应工作流,避免无关提交浪费构建额度。
on:
push:
branches: [ main ]
paths:
- 'server/**'
- '.github/workflows/deploy.yml'
私有仓库每月有2000分钟的免费额度,公有仓库完全免费。合理设计触发条件、控制任务并发数,不仅能加快流水线速度,也能节省额度消耗。建议把main分支设置为受保护分支,要求CI通过后才能合并PR,这样整条流水线才真正起到质量闸门的作用。经过这样一套配置,从代码提交到测试、构建、部署的全流程都能自动化完成,开发者只需要专注于写代码本身。
Node.jsGitHub ActionsCI/CD修改时间:2026-09-16 05:40:31