logo Fonnpo

联系我
2026年5月31日 3 分钟阅读 Fonnpo

GitHub Actions 从入门到实战

核心概念、触发事件、缓存加速 — 以本站 Nuxt 项目为例,实现推送代码自动部署。

#DevOps#CI/CD#GitHub

GitHub Actions 是 GitHub 内置的 CI/CD 平台,让你可以在代码仓库里直接定义自动化流程,无需额外的 Jenkins 或 CircleCI。本文以本站(一个 Nuxt 4 + pnpm 项目)为例,从概念到实战,带你实现推送代码后自动构建并部署到服务器。

核心概念

在写任何配置之前,先把几个术语搞清楚,后面不会绕晕。

概念说明
Workflow一个完整的自动化流程,以 YAML 文件存储在 .github/workflows/ 目录下
Event触发 Workflow 的事件,如 pushpull_requestschedule、手动触发等
JobWorkflow 里的一个执行单元,每个 Job 运行在独立的虚拟机上,默认并行执行
StepJob 里的单个操作,按顺序执行,可以是命令行脚本或已封装好的 Action
Action可复用的操作模块,来自 GitHub Marketplace 或自己编写,用 uses 引用
Runner执行 Job 的虚拟机,GitHub 提供的叫 hosted runner,也可以自己搭 self-hosted
Secret加密的环境变量,在仓库 Settings 里配置,用 ${{ secrets.MY_KEY }} 引用

Workflow 文件结构

所有 Workflow 文件放在仓库根目录的 .github/workflows/ 下,文件名随意,扩展名 .yml.yaml 都行。

一个最简单的结构:

.github/workflows/deploy.yml
        name: Deploy # Workflow 名称,显示在 Actions 页面

on: # 触发条件
  push:
    branches:
      - main

jobs: # 包含一个或多个 Job
  build: # Job ID,自定义命名
    runs-on: ubuntu-latest # 使用哪种 Runner
    steps: # 该 Job 的执行步骤
      - name: 检出代码
        uses: actions/checkout@v4

      - name: 打印 Node 版本
        run: node -v

    

触发事件详解

Yaml
        on:
  # 推送到指定分支时触发
  push:
    branches:
      - main
      - 'release/**' # 支持 glob 匹配
    paths:
      - 'app/**' # 只有这些路径变化才触发(可选)

  # PR 操作时触发
  pull_request:
    branches:
      - main
    types: [opened, synchronize, reopened]

  # 定时触发(cron 语法,UTC 时区)
  schedule:
    - cron: '0 2 * * *' # 每天 UTC 02:00

  # 手动触发(在 Actions 页面点按钮)
  workflow_dispatch:
    inputs:
      environment:
        description: 部署环境
        required: true
        default: production
        type: choice
        options:
          - production
          - staging

    

实战:为本站配置 CI/CD

本站是一个 Nuxt 4 项目,使用 pnpm 管理依赖,用 PM2 在服务器上运行。目标:

  1. CI(代码检查):每次 PR 或推送时,自动执行 ESLint 检查
  2. CD(自动部署):推送到 main 分支时,在 Runner 上完成构建,打包成压缩包通过 SCP 上传到服务器,再 SSH 解压并重启 PM2

第一步:准备 Secrets

在 GitHub 仓库的 Settings → Secrets and variables → Actions 里添加以下变量:

Secret 名称内容
SSH_HOST服务器 IP 或域名,如 123.45.67.89
SSH_PORTSSH 端口,默认 22
SSH_USER登录用户名,如 rootdeploy
SSH_KEY服务器私钥内容(cat ~/.ssh/id_ed25519
SSH_PATH服务器部署目录,如 /www/wwwroot/fonnpo-web

私钥配置说明:把本机私钥 ~/.ssh/id_ed25519完整内容(包括 -----BEGIN...-----END... 行)粘贴到 SSH_KEY 里,然后把对应公钥 ~/.ssh/id_ed25519.pub 追加到服务器的 ~/.ssh/authorized_keys 文件中。

第二步:CI Workflow(代码检查)

.github/workflows/ci.yml
        name: CI

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

jobs:
  lint:
    name: ESLint 检查
    runs-on: ubuntu-latest

    steps:
      - name: 检出代码
        uses: actions/checkout@v4

      - name: 安装 pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10

      - name: 安装 Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm # 缓存 pnpm store,加速后续安装

      - name: 安装依赖
        run: pnpm install --frozen-lockfile

      - name: 运行 ESLint
        run: pnpm eslint .

    
收起

--frozen-lockfile 会确保 CI 环境严格按照 pnpm-lock.yaml 安装,版本一致,不会悄悄升级。

第三步:CD Workflow(自动部署)

本站采用的策略是在 Runner 上完成构建,再把产物打包传到服务器,而不是在服务器上执行 git pull + build。好处是:服务器不需要维护 Node.js 构建环境,构建失败不影响线上服务,服务器压力也更小。

.github/workflows/deploy.yml
        name: Deploy to Self-hosted Server

on:
  push:
    branches: [main]
  workflow_dispatch: # 支持在 Actions 页面手动触发

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true # 强制 Actions 运行时使用 Node 24
      NODE_OPTIONS: --max-old-space-size=4096 # 构建大项目时防止 OOM

    steps:
      - name: 检出代码
        uses: actions/checkout@v4

      - name: 安装 pnpm
        uses: pnpm/action-setup@v4

      - name: 安装 Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm

      - name: 安装依赖
        run: pnpm install --frozen-lockfile

      - name: 构建
        run: pnpm build

      - name: 打包产物
        run: |
          tar -czf release.tgz .output ecosystem.config.cjs package.json pnpm-lock.yaml

      - name: 上传到服务器(SCP)
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_KEY }}
          port: ${{ secrets.SSH_PORT }}
          source: release.tgz
          target: ${{ secrets.SSH_PATH }}

      - name: 服务器端解压并重启
        uses: appleboy/ssh-action@v1.2.0
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_KEY }}
          port: ${{ secrets.SSH_PORT }}
          script: |
            set -e
            cd ${{ secrets.SSH_PATH }}
            tar -xzf release.tgz
            rm -f release.tgz

            corepack enable
            pnpm install --prod --frozen-lockfile

            pm2 startOrReload ecosystem.config.cjs --update-env
            pm2 save

    
收起

几个关键点:

  • envFORCE_JAVASCRIPT_ACTIONS_TO_NODE24 让 Actions 内置运行时对齐 Node 24;NODE_OPTIONS 扩大堆内存上限,防止 Nuxt 构建大项目时 OOM
  • tar 打包:只打包 .output(Nuxt 构建产物)、ecosystem.config.cjspackage.jsonpnpm-lock.yaml,源码不上传
  • appleboy/scp-action:通过 SCP 协议把压缩包传到服务器的 SSH_PATH 目录
  • pnpm install --prod:服务器上只装生产依赖,不装构建工具,减少体积
  • pm2 startOrReload:比 pm2 reload 更健壮,进程不存在时会新建,存在时平滑重载,不会中断服务;pm2 save 保存进程列表,服务器重启后能自动恢复

第四步:合并成一个文件(先 CI 再 CD)

如果希望只有 CI 通过才执行部署,可以用 needs 让 Job 串行:

.github/workflows/ci-cd.yml
        name: CI / CD

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

jobs:
  lint:
    name: ESLint 检查
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
        with:
          version: 10
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm eslint .

  deploy:
    name: 部署到生产服务器
    runs-on: ubuntu-latest
    needs: lint # ← 等 lint Job 成功后才执行
    if: github.ref == 'refs/heads/main' # ← PR 不触发部署,只有推送 main 才部署
    env:
      FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
      NODE_OPTIONS: --max-old-space-size=4096

    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm build

      - name: 打包产物
        run: tar -czf release.tgz .output ecosystem.config.cjs package.json pnpm-lock.yaml

      - name: 上传到服务器(SCP)
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_KEY }}
          port: ${{ secrets.SSH_PORT }}
          source: release.tgz
          target: ${{ secrets.SSH_PATH }}

      - name: 服务器端解压并重启
        uses: appleboy/ssh-action@v1.2.0
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_KEY }}
          port: ${{ secrets.SSH_PORT }}
          script: |
            set -e
            cd ${{ secrets.SSH_PATH }}
            tar -xzf release.tgz && rm -f release.tgz
            corepack enable
            pnpm install --prod --frozen-lockfile
            pm2 startOrReload ecosystem.config.cjs --update-env
            pm2 save

    
收起

这样 PR 只会触发 lint,合并到 main 后会先跑 lint 再跑 deploy,两步都绿才算成功。

常用技巧

缓存依赖加速构建

pnpm 配合 actions/setup-nodecache: 'pnpm' 可以自动缓存 store,大幅减少 pnpm install 时间:

Yaml
        - uses: actions/setup-node@v4
  with:
    node-version: 22
    cache: pnpm # 自动根据 pnpm-lock.yaml 做缓存 key

    

环境变量与 Secrets 的区别

Yaml
        env:
  NODE_ENV: production # 明文,会显示在日志里

steps:
  - run: echo ${{ secrets.TOKEN }} # Secret,日志中自动打码为 ***

    

普通配置用 env,敏感信息(密码、Token、私钥)一定要用 Secrets。

手动触发部署

有时候不想推代码,只想重新部署(比如服务器挂了重启后),可以在 Actions 页面手动触发:

Yaml
        on:
  workflow_dispatch: # 加上这一行,Actions 页面就会出现 "Run workflow" 按钮

  push:
    branches: [main]

    

跨 Job 传递数据

Job 之间是隔离的,通过 outputs 共享数据:

Yaml
        jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.get-version.outputs.version }}
    steps:
      - id: get-version
        run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - run: echo "部署版本 ${{ needs.build.outputs.version }}"

    

只在特定路径变更时触发

前端和后端在同一个仓库时,可以只在相关文件变化时触发对应的流程:

Yaml
        on:
  push:
    paths:
      - 'app/**'
      - nuxt.config.ts
      - package.json
      - pnpm-lock.yaml
    branches: [main]

    

查看执行结果

推送代码后,在 GitHub 仓库的 Actions 标签页可以看到所有 Workflow 的运行记录,点进去可以展开每个 Step 查看完整日志。失败时日志会标红并高亮错误信息,方便排查。

配置好后的完整 Workflow 大概长这样:

Text
        Actions
└── Deploy to Self-hosted Server
    ├── 🟢 lint (20s)
    │   ├── 检出代码
    │   ├── 安装 pnpm / Node.js
    │   ├── pnpm install
    │   └── ESLint 检查
    └── 🟢 deploy (3min)
        ├── 检出代码
        ├── 安装 pnpm / Node.js
        ├── pnpm install
        ├── pnpm build
        ├── tar 打包产物
        ├── SCP 上传到服务器
        └── SSH 解压并重启 PM2
            ├── tar -xzf release.tgz
            ├── pnpm install --prod
            ├── pm2 startOrReload
            └── pm2 save

    

第一次配置好,之后每次 git push 就是全自动,省去手动登录服务器的麻烦。

Fonnpo