GitHub Actions 实战:给个人项目接上免费的自动部署流水线
别再手动
scp了,让每次git push自动把项目部署上去。
我早期更新个人网站都是本地构建完,手动 scp 到服务器、再 ssh 进去重启,经常忘了哪一步就部署了个半成品。后来接上 GitHub Actions,现在只要推代码,构建、测试、部署全自动跑。这篇给初学者一份能直接抄的流水线模板。
不管你是做课程设计、接外包还是维护个人主页,只要代码放在 GitHub 上,这套思路都能用:把"部署"这件重复又容易出错的事交给机器。你只需要关心代码本身,剩下的交给流水线。下面所有片段都经过我实际跑通,复制后改改密钥和路径就能用。
一、免费额度与适用场景
GitHub Actions 对个人开发者非常友好:
| 项目 | 公开仓库 | 私有仓库 |
|---|---|---|
| 每月免费分钟数 | 无限 | 2000 分钟 |
| 存储 | 无限 | 500 MB |
| 自带 runner | 2 核 / 7 GB | 同左 |
| 自托管 runner | 免费、无限制 | 免费、无限制 |
适用场景:前端站自动构建部署、Node 服务推到服务器、跑单测做提交门禁、自动发版打 tag。个人项目基本够用,真不够可以挂一台自己的机器当自托管 runner。
二、workflow 文件长什么样
所有流水线都放在仓库的 .github/workflows/ 目录下,文件名随意、后缀 .yml。一个最基础的骨架:
yaml
name: CI # 流水线名字,显示在 Actions 页
on: # 什么时候触发
push:
branches: [main] # 只有推到 main 才跑
pull_request:
branches: [main]
jobs: # 一个 workflow 由多个 job 组成
build:
runs-on: ubuntu-latest # 用官方提供的 Linux runner
steps: # 顺序执行的步骤
- uses: actions/checkout@v4 # 拉取代码
- run: echo "在这里写命令"
三个核心概念:on 决定触发时机,jobs 是并行任务单元,steps 是具体动作(uses 引用别人写好的 action,run 跑 shell 命令)。
关于触发时机还有几个常用写法:on.push.tags 可以在打 tag 时触发发版;on.workflow_dispatch 能让你在网页上手动点按钮跑一次,调试流水线特别方便;on.pull_request 适合只在提 PR 时跑测试、不部署。多个 job 之间默认并行,如果一个要等另一个(比如先测试通过再部署),用 needs: build 声明依赖即可。另外加 concurrency 能防止多次快速推送时部署任务叠罗汉:
yaml
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: true # 新推送进来就取消还在跑的旧部署
三、跑构建与测试(checkout + setup-node)
下面是一个 Node 项目的标准 CI:拉代码 → 装 Node → 装依赖 → 跑测试。
yaml
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: 拉取代码
uses: actions/checkout@v4
- name: 安装 Node 20
uses: actions/setup-node@v4
with:
node-version: 20
- name: 安装依赖
run: npm ci # ci 比 install 更严格,锁定 lockfile
- name: 构建
run: npm run build
- name: 跑测试
run: npm test
npm ci 要求必须有 package-lock.json,且版本不符会直接报错------这正好帮你在 CI 里卡住"依赖对不上"的问题。测试挂了,整个 job 标红,push 就不会被误判成功。
四、把产物部署到服务器(ssh + secrets)
构建完要部署到自己的云服务器,用 appleboy/ssh-action 通过 SSH 执行远程命令。密钥绝对不能写进仓库,统一放仓库 Settings → Secrets and variables → Actions 里:
yaml
- name: 部署到服务器
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.HOST }} # 服务器 IP
username: ${{ secrets.USERNAME }} # 如 root
key: ${{ secrets.SSH_KEY }} # 私钥内容,非密码
script: |
cd /home/app/my-project
git pull
docker compose pull
docker compose up -d
docker image prune -f
secrets.SSH_KEY 放的是你本地 ~/.ssh/id_rsa 的私钥内容 (不是路径)。生成密钥对:ssh-keygen -t ed25519,把公钥 id_rsa.pub 追加到服务器的 ~/.ssh/authorized_keys,私钥整段粘进 secret。
五、部署到 Pages(GitHub Pages / Cloudflare Pages)
纯静态站(Vite、Vue、React 构建产物)没必要上服务器,直接丢 Pages 最省事。
GitHub Pages 需要额外加权限声明,否则会部署失败:
yaml
permissions:
contents: read
pages: write
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: github-pages
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci && npm run build
- name: 上传产物
uses: actions/upload-pages-artifact@v3
with:
path: ./dist # 构建输出目录
- name: 部署
uses: actions/deploy-pages@v4
Cloudflare Pages 用 wrangler 推上去,连服务器都不用买:
yaml
- name: 部署到 Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
command: pages deploy dist --project-name=my-site
六、缓存依赖、矩阵构建与分支触发
缓存依赖能显著加速,避免每次都重新下载:
yaml
- name: 缓存 npm
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
矩阵构建一次性验证多版本 Node,证明兼容性:
yaml
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci && npm test
只让 main 触发部署 (避免 feature 分支也去动服务器):把部署 job 的触发单独限制,或者用 if: github.ref == 'refs/heads/main' 做条件判断。我习惯把 CI 和 CD 拆成两个文件:测试任何分支都跑,部署只在 main 上跑。
七、常见坑逐个排
报错 1:workflow 完全不触发
先确认文件在 .github/workflows/ 下且是合法 YAML;再确认 on.push.branches 写的就是你推送的分支名(很多人默认写了 main,实际仓库叫 master)。本地可用 yaml 校验语法。
报错 2:权限不足 / 403
Pages 部署忘记加 permissions 块是最常见的。自托管 runner 跑 docker 也要把用户加进 docker 组。
报错 3:secrets 读不到
secrets.XXX 只能在 with / run 里用,不能直接在 if 等部分上下文裸用;另外确认 secret 名字拼写和仓库设置里完全一致(大小写敏感)。
报错 4:部署成功但页面还是旧的
八成是 CD 没清缓存,或者部署的是 dist 但构建输出其实是 build。本地先 npm run build 看真实输出目录;Pages 注意开"每次部署前清除旧文件"。
报错 5:超时失败
单 job 默认超时 360 分钟、整个 runner 有 6 小时上限。装依赖卡住时优先加缓存;海外源慢就换国内镜像:npm config set registry https://registry.npmmirror.com。
收个尾:流水线不是越复杂越好,先把"push main → 自动部署"这条最短路跑通,再慢慢加测试和缓存。今晚就给你的个人项目建一个 .github/workflows/deploy.yml,把 HOST、USERNAME、SSH_KEY 三个 secret 配好,推一次代码验证它真的自动上线了。