📚 全栈开发学习系列
第 34 篇 · 阶段五:扩展与部署
✅ 阶段四:跨平台 App(26-30篇)
已完成
🏆 阶段五:扩展与部署
已完成
✓ 31 Tauri 桌面应用入门
✓ 32 Docker 容器化入门
✓ 33 Nginx 反向代理
✓ 34 CI/CD 自动化部署
读完本篇你将能:
✓ 理解 CI/CD 的核心理念与流水线阶段
✓ 掌握 GitHub Actions 工作流配置语法
✓ 搭建一条完整的前端 CI 流水线(lint → test → build)
✓ 实现自动化部署到服务器(SSH + Docker)
✓ 了解环境变量、缓存优化与安全最佳实践
CI/CD 自动化部署:从代码提交到一键上线
全栈开发学习系列 · 第 34 篇 · 阶段五收官
📑 本篇目录
一、为什么需要 CI/CD:从手动部署的痛点说起
二、核心概念:CI、CD、流水线、工件
三、GitHub Actions 入门:工作流配置语法
四、实战一:前端项目 CI 流水线(Lint → Test → Build)
五、实战二:自动化部署到服务器(SSH + Docker)
六、进阶技巧:缓存、矩阵构建与环境变量
七、安全最佳实践:Secrets 与权限控制
八、常见错误与实战练习
一、为什么需要 CI/CD:从手动部署的痛点说起
回想一下没有自动化的日子:开发完成后,你需要手动 SSH 到服务器,拉代码、装依赖、构建、重启服务,还要祈祷这次别出问题。如果是多人协作,还得协调部署时间,生怕两个人同时改导致冲突。
手动部署的痛点几乎每个团队都经历过:
- 容易出错:漏了一步命令、环境不一样、忘了清缓存,都可能导致线上挂掉
- 效率低下:每次部署要十几分钟,还得有人盯着日志
- 不可追溯:上次部署了什么代码?谁部署的?什么时候部署的?全靠记忆
- 发布焦虑:越晚越不敢发版,越攒越多问题,越容易出事故
💡 生活中的比喻
CI/CD 就像工厂的自动化流水线。以前手工做产品,每个工人凭经验组装,质量参差不齐,效率也低。有了流水线后,原料从一端进去,经过一道道标准化工序(检查、组装、测试、包装),合格的产品从另一端自动出来。任何一道工序不达标,流水线就会自动停下来报警。
CI/CD 就是软件开发的"自动化流水线"。代码提交后,自动触发一系列检查和操作,全部通过后自动部署到生产环境。整个过程不需要人工干预,又快又稳。
2026 年 CI/CD 工具格局
目前主流的 CI/CD 平台各有侧重:[$TRAE_REF](https://devstarsj.github.io/devops/ci-cd/2026/03/15/cicd-comparison-github-actions-vs-gitlab-vs-circleci-vs-dagger-2026/)
| 平台 |
优势 |
适用场景 |
免费额度 |
| GitHub Actions |
生态最丰富,与 GitHub 深度集成 |
代码托管在 GitHub 的项目 |
2000 分钟/月 |
| GitLab CI |
一体化 DevSecOps 平台 |
企业级自托管需求 |
400 分钟/月 |
| CircleCI |
测试性能强,资源灵活 |
对构建速度要求高 |
1500 分钟/月 |
| Dagger |
可移植性强,本地可调试 |
跨平台迁移、本地调试 |
开源免费 |
本篇我们以 GitHub Actions 为主线讲解——它是目前最流行、生态最丰富的选择,而且对大多数项目来说免费额度完全够用。[$TRAE_REF](https://mecanik.dev/en/posts/ci-cd-pipeline-best-practices-for-uk-development-teams-in-2026/)
二、核心概念:CI、CD、流水线、工件
在动手之前,先搞清楚几个经常被混用的概念。
CI(持续集成)
Continuous Integration,持续集成。核心思想是:开发者频繁地把代码合并到主干,每次合并都自动构建和测试。
以前大家各写各的,到发布前才合并,一合并就是一大堆冲突,修都修不过来。CI 要求"小步快跑"——每天多次提交,每次提交后自动跑一遍检查,有问题立刻暴露,而不是攒到最后一起爆发。
CD(持续交付 / 持续部署)
CD 有两层含义,经常被混淆:
📦 持续交付
Continuous Delivery
代码随时可以安全发布,但发布按钮还是人来按。确保"随时可发",发不发由人决定。
🚀 持续部署
Continuous Deployment
代码通过所有检查后,自动部署到生产环境。全程无人干预,真正的"一键上线"。
流水线的典型阶段
一条完整的 CI/CD 流水线通常包含以下阶段,从前到后依次执行,任何一个阶段失败就中止:
| 阶段 |
做什么 |
失败了怎么办 |
| 检出代码 |
拉取代码、检出对应分支 |
仓库权限或网络问题 |
| 安装依赖 |
npm install / pip install 等 |
依赖包不存在或版本冲突 |
| 代码检查 |
Lint、格式检查、类型检查 |
代码风格不达标,修复后重提 |
| 单元测试 |
运行所有单元测试用例 |
测试不通过,修复 bug |
| 构建 |
编译、打包、生成可部署产物 |
构建失败,检查代码 |
| 部署 |
把产物部署到目标环境 |
部署失败,回滚到上一版本 |
什么是工件(Artifact)
流水线的每个阶段可能会产生一些中间文件,比如编译后的代码、测试报告、打包好的镜像等。这些文件就叫 工件(Artifact)。
工件可以在流水线的不同 Job 之间传递,也可以保留下来供后续下载排查。比如构建阶段产出的 dist 目录,可以传给部署阶段用来发布。
三、GitHub Actions 入门:工作流配置语法
GitHub Actions 是 GitHub 内置的 CI/CD 工具,最大的优势是和 GitHub 深度集成——代码推上去自动触发,不需要额外的账号和服务器。配置文件是 YAML 格式,放在仓库的 .github/workflows/ 目录下。
核心概念
- Workflow(工作流):一整套自动化流程,对应一个 YAML 文件
- Event(触发事件):什么情况下触发工作流,比如 push、pull_request、定时等
- Job(任务):工作流中的一个独立执行单元,可以有多个 Job
- Step(步骤):Job 里的每一条具体命令或操作
- Action(动作):可复用的步骤单元,别人写好的你直接用
- Runner(运行器):执行工作流的机器,GitHub 提供免费的托管 Runner
最简工作流示例
YAML - .github/workflows/ci.yml
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# 工作流名称(显示在 Actions 页面)
name: CI Pipeline
# 触发条件
on:
push: # 代码 push 时触发
branches: ["main"] # 只监听 main 分支
pull_request: # 提交 PR 时触发
branches: ["main"]
# 任务定义
jobs:
test: # 任务名(自定义)
runs-on: ubuntu-latest # 运行环境
steps: # 步骤列表
- uses: actions/checkout@v4 # 第一步:检出代码 ⭐
- run: echo "Hello, GitHub Actions!" # 第二步:执行命令
|
常用触发事件
| 事件 |
触发时机 |
常见用途 |
| push |
代码推送时 |
CI 检查、自动构建 |
| pull_request |
创建/更新 PR 时 |
合并前检查 |
| schedule |
定时触发(cron 语法) |
定时任务、夜间构建 |
| workflow_dispatch |
手动点击触发 |
手动部署、按需运行 |
| release |
发布 Release 时 |
发布时自动部署生产环境 |
常用 Action 速查
Action 是 GitHub Actions 的精髓——社区已经写好了成千上万的可复用动作,你直接 uses 就行:
| Action |
用途 |
说明 |
| actions/checkout@v4 |
检出代码 |
几乎每个工作流第一步都要用到 |
| actions/setup-node@v4 |
安装 Node.js |
指定版本,支持缓存 npm 依赖 |
| actions/cache@v4 |
缓存依赖 |
大幅提升构建速度 |
| actions/upload-artifact@v4 |
上传工件 |
在 Job 间传递文件 |
| actions/download-artifact@v4 |
下载工件 |
配合 upload 使用 |
| appleboy/ssh-action@v1.2.0 |
SSH 远程执行 |
部署到自己的服务器 |
四、实战一:前端项目 CI 流水线(Lint → Test → Build)
理论说够了,来实战。我们给一个 React + TypeScript + Vite 项目搭建一条完整的 CI 流水线,包含代码检查、单元测试和构建验证。
完整工作流配置
YAML - .github/workflows/ci.yml
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
name: Frontend CI
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
jobs:
lint-and-test:
runs-on: ubuntu-latest
strategy: # 矩阵构建:多个 Node 版本
matrix:
node-version: ["18", "20", "22"]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
|
继续看后面的步骤:
YAML - 续上:安装依赖、Lint、Test、Build
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "npm" # 自动缓存 npm 依赖 ⭐
- name: Install dependencies
run: npm ci # ci 比 install 更快更严格
- name: Run linter
run: npm run lint
- name: Type check
run: npx tsc --noEmit
- name: Run unit tests
run: npm test -- --coverage --ci
- name: Build project
run: npm run build
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
|
💡 npm install vs npm ci
CI 环境中推荐用 npm ci 而不是 npm install。区别是:ci 会严格按照 package-lock.json 安装,版本完全一致,不会自动更新锁文件,确保每次构建的依赖都一样。而且 ci 比 install 更快,因为它跳过了某些版本协商逻辑。
五、实战二:自动化部署到服务器(SSH + Docker)
CI 通过了只是第一步,最终目的是把代码部署到服务器。最常见的方式是通过 SSH 连接服务器,然后执行部署命令。配合 Docker 的话,部署流程会更优雅。
方案一:SSH 直接部署
这是最简单直接的方式:GitHub Actions 通过 SSH 登录到你的服务器,执行拉代码、构建、重启等命令。
YAML - .github/workflows/deploy.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
name: Deploy to Server
on:
push:
branches: ["main"] # 只有 main 分支才部署
workflow_dispatch: # 支持手动触发
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
# 通过 SSH 连接服务器并执行部署脚本
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.2.0
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT }}
|
SSH 命令部分继续:
YAML - 续上:SSH 执行部署命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
script: |
# 进入项目目录
cd /var/www/my-app
# 拉取最新代码
git pull origin main
# 安装依赖并构建
npm ci
npm run build
# 用 Docker Compose 重启服务
docker compose up -d --build
docker image prune -f
|
配置 Secrets(密钥)
注意上面的 ${{ secrets.XXX }} 语法——这些是存在 GitHub 仓库设置里的密钥,不会明文出现在代码和日志中。
配置位置:GitHub 仓库 → Settings → Secrets and variables → Actions → New repository secret
- SERVER_HOST:服务器 IP 地址或域名
- SERVER_USER:SSH 登录用户名
- SSH_PRIVATE_KEY:SSH 私钥内容(
cat ~/.ssh/id_rsa 全部内容)
- SSH_PORT:SSH 端口,默认 22
💡 安全小贴士
永远不要把密码、密钥、Token 等敏感信息直接写在 YAML 文件里。一定要用 Secrets 机制存储。GitHub 会自动在日志中屏蔽 Secrets 的值,即使不小心打印出来也会显示为 ***。
方案二:Docker 镜像 + 部署
更规范的做法是在 CI 中构建 Docker 镜像,推送到镜像仓库(如 Docker Hub、GitHub Container Registry),然后在服务器上拉取新镜像重启。这样做的好处是构建和部署分离,镜像可以复用,可以回滚到任意版本。
YAML - 构建 Docker 镜像并推送到 GHCR
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
|
六、进阶技巧:缓存、矩阵构建与环境变量
依赖缓存:显著提速
每次 CI 都重新安装依赖是最大的时间浪费。用好缓存可以节省 80% 以上的安装时间。setup-node 的 cache: "npm" 参数已经帮你缓存了 npm 依赖,但如果需要更细粒度的控制,可以手动配置:
YAML - 手动缓存 node_modules
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Cache node_modules
uses: actions/cache@v4
id: cache-deps
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
if: steps.cache-deps.outputs.cache-hit != 'true'
run: npm ci
|
缓存的 key 由 package-lock.json 的哈希值决定——锁文件变了缓存就失效,确保依赖版本一致。restore-keys 提供了"降级匹配",即使精确 key 没命中,也能恢复最近的缓存,加快安装速度。
矩阵构建(Matrix)
想同时在多个 Node 版本、多个操作系统上测试?矩阵构建可以让你一份配置自动生成多组并行任务:
YAML - 矩阵构建设置
|
1
2
3
4
5
6
7
8
9
10
|
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false # 一个失败不取消其他任务
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: ["18", "20", "22"]
exclude: # 排除某些组合
- os: windows-latest
node-version: "18"
|
上面的配置会生成 3 × 3 - 1 = 8 个并行任务(排除了 Windows + Node 18 的组合),同时在不同系统和 Node 版本上运行测试,确保兼容性。
环境变量与上下文
GitHub Actions 提供了丰富的内置变量和上下文,可以在工作流中直接使用:
| 变量 |
说明 |
示例值 |
| github.actor |
触发工作流的用户名 |
octocat |
| github.sha |
当前提交的完整 SHA |
a1b2c3d4... |
| github.ref |
当前分支或标签引用 |
refs/heads/main |
| github.run_number |
工作流运行次数 |
42 |
| runner.os |
当前运行器操作系统 |
Linux |
| secrets.GITHUB_TOKEN |
自动生成的仓库访问令牌 |
(自动注入) |
七、安全最佳实践:Secrets 与权限控制
CI/CD 流水线天然拥有访问代码、服务器、云资源的权限,一旦被攻破后果严重。安全是 CI/CD 设计中不可忽视的环节。2026 年的趋势是 DevSecOps——安全不再是最后一道门,而是左移到流水线的每个阶段。[$TRAE_REF](https://ai.gravitydevops.com/articles/best-cicd-platforms-2026)
Secrets 管理的最佳实践
- 最小权限原则:给 CI 用的密钥只授予必要的最小权限,不要用管理员账号
- 定期轮换:SSH 密钥、Access Token 等要定期更换,建议 90 天一次
- 不同环境隔离:测试环境和生产环境用不同的密钥,避免一个泄露全遭殃
- Environment Secrets:用 GitHub Environments 功能,把密钥和环境绑定,还可以加审批流程
- 禁止打印 Secrets:虽然 GitHub 会自动屏蔽,但还是不要在脚本里 echo 敏感信息
流水线安全加固
YAML - 安全加固配置示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
name: Secure Deploy
on:
push:
branches: ["main"]
# 限制 GITHUB_TOKEN 的默认权限(最小权限)
permissions:
contents: read # 只读代码
packages: write # 可以推送镜像
security-events: write # 安全扫描结果
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
# 依赖漏洞扫描
|
PR 流水线的安全风险
开放源码项目特别要注意:外部贡献者提交的 PR 也会触发 CI,如果流水线里有 Secrets,恶意攻击者可以通过 PR 提交代码把 Secrets 偷出来。
GitHub 的默认保护机制是:外部贡献者的 PR 首次提交需要仓库成员手动批准才能运行工作流。但即便如此,也建议遵循以下原则:
- PR 流水线只跑 lint 和 test,不部署、不访问生产 Secrets
- 敏感操作放在
pull_request_target 事件中(使用基础分支的代码和 Secrets)
- 部署流水线只在 main 分支 push 时触发,不在 PR 时触发
- 开启 Environment 的 required reviewers,生产部署需要人工审批
八、常见错误与实战练习
常见踩坑记录
❌ 错误 1:YAML 缩进错误
症状:工作流文件无法解析,报错 "unexpected key" 或 "mapping values are not allowed here"
常见原因:YAML 对缩进非常敏感,必须用空格(不能用 Tab),层级要对齐
排查技巧:
- 用支持 YAML 的编辑器(VS Code 装 YAML 插件),会实时报错
- 同一层级的键必须左对齐
- 列表项(- 开头)比父级缩进 2 个空格,列表内容再缩进 2 个
- 在线工具 yamllint.com 可以快速验证语法
❌ 错误 2:Secrets 不生效
症状:用了
${{ secrets.XXX }} 但值是空的,或者报 undefined
常见原因:Secrets 名字写错了大小写,或者配置在了错误的位置
排查步骤:
- 确认 Secrets 存在于正确的仓库(不是个人设置里的 Secrets)
- Secrets 名字区分大小写,检查拼写
- 如果用的是 Environment Secrets,需要在 job 中指定
environment: production
- 不要直接 echo Secrets 来调试(会被屏蔽为 ***),可以检查是否为空:
test -n "$SECRET" && echo "exists" || echo "missing"
❌ 错误 3:本地能跑 CI 失败
症状:本地 npm test 一切正常,CI 里却报错
常见原因:操作系统不同、Node 版本不同、依赖不一致、缺少环境变量
排查步骤:
- 确认 CI 用的 Node 版本和本地一致(用 .nvmrc + setup-node 的 node-version-file)
- 用
npm ci 而不是 npm install,确保依赖完全一致
- 检查 CI 环境是否有项目需要的环境变量
- 注意文件路径的大小写:Linux 区分大小写,macOS/Windows 不区分
- 用 act(可搜索 "nektos/act" 了解详情) 工具在本地运行 GitHub Actions 调试
动手练习
🏋️ 练习:为你的项目搭建 CI/CD 流水线
目标:给你之前的项目加一条完整的 CI/CD 流水线
基础要求(必做):
1. 创建 .github/workflows/ci.yml,在 push 和 PR 时触发
2. 包含:安装依赖 → Lint → 类型检查 → 单元测试 → 构建
3. 开启依赖缓存,观察第二次运行是否提速
4. 故意提交一段有语法错误的代码,验证 CI 是否会失败
进阶挑战(选做):
1. 用矩阵构建测试多个 Node 版本
2. 配置部署流水线,推送到自己的服务器
3. 配置 Environment 和人工审批
4. 加入代码覆盖率报告上传
验证方法:打开仓库 Actions 页面,查看工作流运行日志
📝 知识回顾
核心概念
CI 持续集成、CD 持续交付/部署、流水线、工件、Runner
工作流结构
on 触发 → jobs 任务 → steps 步骤 → uses/run 执行
常用 Action
checkout@v4、setup-node@v4、cache@v4、upload-artifact@v4
部署方式
SSH 远程执行、Docker 镜像推送、GitHub Pages 部署
优化手段
依赖缓存、矩阵构建、并行 Job、npm ci 替代 install
安全实践
Secrets 管理、最小权限、PR 安全、Environment 审批
🎯 阶段五 · 扩展与部署 · 完
恭喜你完成了全栈开发学习系列的最后一个阶段!
从桌面应用到容器化部署,从反向代理到自动化流水线
你已经拥有了从开发到部署的完整工程能力
34 篇文章 · 5 个阶段 · 从零基础到全栈工程师
全栈开发学习系列 · 完结撒花 🎉
感谢一路陪伴,我们下个系列见