Skip to content

GitHub Actions

工作原理

GitHub Actions 是 GitHub 内置的 CI/CD 自动化平台。每次推送代码后,不需要手动连接服务器,GitHub 会自动帮你完成构建、测试、部署等一系列操作。

完整执行过程:

  1. 开发者推送代码到 GitHub 仓库
  2. GitHub 检测到触发事件(如 push 到 main 分支)
  3. 分配一台托管虚拟机(Runner),克隆仓库代码
  4. Runner 按工作流文件依次执行 Job 和 Step
  5. 执行结果记录到仓库的 Actions 选项卡,可配置失败通知
概念说明
Workflow工作流,定义在 .github/workflows/*.yml 中,由事件触发
Job工作流中的任务,默认并行运行,可用 needs 设置依赖顺序
StepJob 中的单个步骤,按顺序执行,可运行命令或调用 Action
Action可复用的操作单元,来自 GitHub Marketplace 或自定义
Runner执行 Job 的虚拟机,GitHub 提供 ubuntu/windows/macos 三类
Secret仓库级加密变量,在工作流中用 ${{ secrets.NAME }} 引用

每个仓库最多 20 个工作流同时运行,超出部分会被 GitHub 自动暂停。

GitHub 仓库配置

GitHub Actions 在公开仓库默认开启,私有仓库也默认可用(Free 计划每月有 2000 分钟免费额度)。使用前需确认以下两项配置。

开启 Actions 权限

进入仓库 → SettingsActionsGeneral,确认 Actions permissions 选择的是 Allow all actions and reusable workflows(默认值)。如果是组织仓库且 Actions 被禁用,需要由组织管理员在组织设置中开启。

工作流权限(GITHUB_TOKEN)

每次工作流运行时,GitHub 会自动注入一个临时 Token(${{ secrets.GITHUB_TOKEN }}),用于读写仓库内容(如发布 Release、推送到 gh-pages 分支)。默认权限为只读,如需写入,进入仓库 → SettingsActionsGeneralWorkflow permissions,选择 Read and write permissions

自己配置的 Secrets(如 SSH_PRIVATE_KEY)与 GITHUB_TOKEN 是两套不同的机制:前者是手动添加的敏感变量,后者是 GitHub 自动颁发的临时凭证。

查看运行结果

工作流触发后,在仓库顶部点击 Actions 选项卡,可以看到所有历史运行记录。点击某次运行,展开对应 Job 和 Step,可以实时查看日志输出,失败时会高亮显示具体报错行。

工作流配置

文件位置与命名

工作流文件存放在仓库的 .github/workflows/ 目录下,使用 .yml 格式,文件名建议与用途对应:

bash
.github/
 workflows/
     deploy.yml      # 部署到服务器
     ci.yml          # 持续集成(lint / 测试)

每个工作流文件独立触发、独立执行,互不影响。


触发事件(on)

on 字段定义什么情况下触发这个工作流:

yaml
on:
  # 推送到指定分支时触发(最常用)
  push:
    branches:
      - main
      - 'release/*' # 支持通配符

  # 向指定分支发起 PR 时触发
  pull_request:
    branches:
      - main

  # 定时触发(UTC 时间)
  schedule:
    - cron: '0 2 * * *' # 每天北京时间 10:00(UTC+8 偏移 -8)

  # 允许在 GitHub 界面手动触发
  workflow_dispatch:

常用事件一览:

事件触发时机
push推送提交到指定分支/标签时
pull_requestPR 被创建、更新、关闭时
schedule按 cron 定时触发(最小间隔 5 分钟)
workflow_dispatch手动触发,支持在 GitHub UI 传入参数
release发布新版本、编辑或删除 Release 时

定时任务(cron)

cron 字段格式为 分 时 日 月 周,以 UTC 时间为准:

yaml
on:
  schedule:
    - cron: '0 2 * * *' # 每天 UTC 02:00(北京时间 10:00)
    - cron: '0 2 * * 1-5' # 周一到周五 UTC 02:00
    - cron: '*/30 * * * *' # 每 30 分钟(最小粒度为 5 分钟)
符号含义
*任意值
,多个值,如 1,3,5
-范围,如 1-5
/间隔,如 */15 表示每 15 分钟

GitHub Actions 定时任务在高峰期可能有延迟,不适合对精确时间要求极高的场景。


Job 配置

一个工作流可以包含多个 Job。Job 默认并行,用 needs 可以设置执行顺序:

yaml
jobs:
  build:
    runs-on: ubuntu-latest # 在 Ubuntu 虚拟机上运行
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run build

  deploy:
    runs-on: ubuntu-latest
    needs: build # 等 build 完成后才执行
    if: github.ref == 'refs/heads/main' # 只有 main 分支才部署
    environment: production # 关联 GitHub 环境,可配置审批保护
    timeout-minutes: 30 # 超时自动取消,防止卡死
    steps:
      - run: echo "开始部署"

runs-on 常用值:ubuntu-latestubuntu-22.04windows-latestmacos-latest。完整列表见 runner-images

矩阵策略(同时在多个环境下测试):

yaml
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node: [18, 20, 22] # 并行创建 3 个 Job 实例,分别测试三个版本
    steps:
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - run: npm test

Step 配置

每个 Step 可以执行命令(run)或调用 Action(uses):

yaml
steps:
  - name: 检出代码
    uses: actions/checkout@v4 # 把仓库代码下载到 Runner 上,几乎每个工作流都需要

  - name: 安装 Node.js
    uses: actions/setup-node@v4
    with:
      node-version: '20' # with 为 Action 提供输入参数

  - name: 安装依赖
    run: npm ci # run 执行 Shell 命令

  - name: 多行命令
    run: |
      echo "开始构建..."
      npm run build
      echo "构建完成"

  - name: 设置 Step 级环境变量
    run: echo "打印 $MY_VAR"
    env:
      MY_VAR: hello # 仅本 Step 生效,不污染全局

GitHub Marketplace 可搜索社区提供的各类 Action,如 actions/setup-nodeappleboy/ssh-action 等。

内置变量可在工作流任意位置引用,格式为 ${{ github.XXXX }}

变量说明
${{ github.actor }}触发工作流的用户名
${{ github.repository }}仓库全名(owner/repo
${{ github.ref }}触发的分支(如 refs/heads/main
${{ github.sha }}当前提交的 SHA 值
${{ github.event_name }}触发事件名称(如 pushpull_request

Secrets 配置

Secrets 是仓库级的加密变量,用于存储密码、私钥、Token 等敏感信息。值写入后无法再次查看,只能覆盖或删除。工作流中通过 ${{ secrets.变量名 }} 引用。

添加步骤:

  1. 进入仓库 → SettingsSecrets and variablesActions
  2. 点击 New repository secret
  3. 填写变量名(如 SSH_PRIVATE_KEY)和值,保存

常用 Secrets:

Secret 名称用途
SSH_HOST服务器 IP 地址
SSH_USER服务器登录用户名
SSH_PORTSSH 端口(默认 22)
SSH_PRIVATE_KEYSSH 私钥(用于免密登录服务器)
DEPLOY_PATH服务器上的部署目录路径
WECHAT_WEBHOOK_URL企业微信机器人 Webhook 地址

不要把密码、Token 直接写在 .yml 文件里提交到仓库,一律通过 Secrets 传入。

SSH 免密登录

GitHub Actions 通过 SSH 私钥连接服务器执行远程命令或上传文件,需要提前在服务器上配置对应公钥。整体思路:把公钥存在服务器,把私钥存在 GitHub Secrets,GitHub 用私钥向服务器证明身份。

1. 在服务器上生成密钥对:

bash
ssh-keygen -t ed25519 -C "github-actions-deploy"
# 路径直接回车使用默认(~/.ssh/id_ed25519)
# 密码留空,直接两次回车

生成两个文件:~/.ssh/id_ed25519(私钥)和 ~/.ssh/id_ed25519.pub(公钥)。

也可以用 rsa -b 4096,ed25519 更短更安全,推荐优先使用。


2. 将公钥追加到服务器授权列表:

bash
cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys    # 权限必须为 600,SSH 才会读取此文件
chmod 700 ~/.ssh                    # .ssh 目录权限必须为 700

3. 将私钥添加到 GitHub Secrets:

服务器上执行以下命令,查看私钥内容:

bash
cat ~/.ssh/id_ed25519

输出类似:

text
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAA...(中间内容省略)
-----END OPENSSH PRIVATE KEY-----

全选复制(必须包含首尾的 BEGIN / END 行),然后:

  1. 进入仓库 → SettingsSecrets and variablesActions
  2. 点击 New repository secret
  3. 名称填 SSH_PRIVATE_KEY,值粘贴刚才复制的全部内容,保存

私钥只存在服务器和 GitHub Secrets 中,不要复制到任何其他地方,也不要提交到代码仓库。


4. 验证 SSH 连接(可在本地测试):

bash
ssh -i ~/.ssh/id_ed25519 user@your-server-ip
# 能无密码进入服务器说明配置正确

最佳实践

Vue / VitePress 静态站点部署(构建 + 上传服务器)

Vue、VitePress、React 等前端项目本质是静态文件:npm run build 后生成 dist/ 目录,把这个目录上传到服务器,Nginx 指向它就完成了部署。相比后端服务,静态站点不需要 Docker,构建在 GitHub Runner 上完成,直接传文件即可。

前提准备:

  • 服务器已安装 Nginx 和 rsync(rsync 用于文件同步,缺少则部署报错)
  • 部署目录已创建(如 /var/www/my-site
  • 已完成 SSH 免密登录配置(私钥存在 Secret SSH_PRIVATE_KEY
  • 已添加 Secrets:SSH_HOSTSSH_USERSSH_PORTDEPLOY_PATH

安装 rsync:

bash
sudo apt install rsync   # Ubuntu / Debian
sudo yum install rsync   # CentOS / RHEL

工作流文件 .github/workflows/deploy.yml

yaml
name: Build and Deploy

on:
  push:
    branches:
      - main # 推送到 main 分支时自动触发

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: 检出代码
        uses: actions/checkout@v4
        with:
          fetch-depth: 0 # 默认只拉最新 1 条提交;0 表示拉取完整历史,VitePress lastUpdated 功能需要

      - name: 安装 pnpm
        uses: pnpm/action-setup@v4
        with:
          version: latest # npm 项目删除此 step,后面改用 npm ci

      - name: 安装 Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm' # 缓存 pnpm store 加快依赖安装;npm 项目改为 'npm'

      - name: 安装依赖
        run: pnpm install # npm 项目改为:npm ci

      - name: 构建
        run: pnpm docs:build # VitePress 项目;普通 Vue 项目改为:pnpm build

      - name: 上传到服务器
        uses: appleboy/scp-action@master # 通过 SCP 传输文件到服务器
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          port: ${{ secrets.SSH_PORT }}
          source: '.vitepress/dist/*' # VitePress 产物;Vue 项目改为:dist/*
          target: ${{ secrets.DEPLOY_PATH }}
          strip_components: 2 # 去掉 source 路径中的前两层目录(.vitepress/dist),文件直接落在 target 下

scp-action 的上传行为:

scp-action全量覆盖,每次运行都把 source 匹配的所有文件重新传一遍,不做差量比对。对于静态站点(几十 MB 以内)这没有问题;文件量很大时改用 rsync 做增量同步(见下方说明)。

strip_components 的含义:

strip_components 控制上传时剥去 source 路径前缀的目录层数,等价于 tar --strip-components

以 VitePress 为例,source: '.vitepress/dist/*',Runner 上的目录结构是:

text
.vitepress/
└── dist/               ← 第 1 层
    ├── index.html      ← 文件
    └── assets/         ← 子目录
        └── app.js

source 匹配的文件相对路径是 .vitepress/dist/index.html,其中包含两层目录前缀(.vitepressdist)。strip_components 就是告诉 scp 上传时把这个前缀的前 N 层去掉:

strip_components服务器落地路径(DEPLOY_PATH = /var/www/my-site
不设置(默认 0)/var/www/my-site/.vitepress/dist/index.html
1/var/www/my-site/dist/index.html
2/var/www/my-site/index.html ✓ 推荐

普通 Vue 项目产物在 dist/source: 'dist/*' 只有 1 层前缀,设 strip_components: 1 即可。

指定目标目录:

target 直接写服务器上的绝对路径,strip_components 之后的文件直接落在这个目录下:

yaml
target: /var/www/my-site          # 文件落在 /var/www/my-site/ 下
target: /var/www/my-site/static   # 文件落在 /var/www/my-site/static/ 下

target 目录不存在时 scp-action 会自动创建,但前提是登录用户对父目录有写入权限。

文件量较大时改用 rsync 增量同步:

rsync 只传本次构建中发生变化的文件(按文件大小和修改时间比对),比 scp 全量覆盖快很多。

使用前必须先在服务器上安装 rsync,否则部署时报 rsync: command not found 错误。

bash
sudo apt install rsync   # Ubuntu / Debian
sudo yum install rsync   # CentOS / RHEL

工作流替换上传步骤:

yaml
- name: rsync 增量同步到服务器
  uses: burnett01/rsync-deployments@7.0.1
  with:
    switches: >-
      -avz
      --delete
      --exclude='.git'
    path: .vitepress/dist/ # VitePress 产物目录(末尾加 / 表示只传目录内容)
    remote_path: ${{ secrets.DEPLOY_PATH }}
    remote_host: ${{ secrets.SSH_HOST }}
    remote_user: ${{ secrets.SSH_USER }}
    remote_key: ${{ secrets.SSH_PRIVATE_KEY }}
    remote_port: ${{ secrets.SSH_PORT }}

rsync 常用参数说明:

参数含义
-a归档模式,保留权限、时间戳、软链接等
-v输出传输详情,便于调试
-z传输时压缩,适合带宽较小的云服务器
--delete删除服务器上本次源目录中已不存在的文件

rsync 方案不需要 strip_components,因为 path 末尾加 / 后,rsync 直接传目录内容到 remote_path,不会带目录层级。

服务器 Nginx 配置(Vue SPA / VitePress 通用写法,新建或修改 /etc/nginx/sites-available/my-site):

nginx
server {
    listen 80;
    server_name your-domain.com;    # 替换为实际域名或 IP
    root /var/www/my-site;          # 对应 DEPLOY_PATH

    index index.html;

    location / {
        try_files $uri $uri/ /index.html;   # SPA 路由刷新时不返回 404,交由前端路由处理
    }
}

启用配置并重载 Nginx:

bash
sudo ln -s /etc/nginx/sites-available/my-site /etc/nginx/sites-enabled/
sudo nginx -t          # 检查配置语法
sudo nginx -s reload   # 重载配置

第一次部署前,确保服务器目录存在且有写入权限:

bash
mkdir -p /var/www/my-site
chown -R $USER /var/www/my-site

NestJS 服务部署(SSH 远程执行 + Docker Compose + 企业微信通知)

这是简化方案,构建发生在服务器上,适合个人项目快速上手。生产团队推荐使用下一节的「CI 构建镜像 + GHCR」方案。

NestJS 等后端服务用 Docker Compose 运行,代码更新后需要在服务器上重新拉取代码、重建镜像并启动容器。与前端不同,后端的构建发生在服务器上docker compose up --build),所以 GitHub Actions 这边只需 SSH 进去执行命令即可。

通知逻辑分两层:

  • SSH 脚本内部捕获 docker compose 的退出码,部署成功/失败分别通知
  • if: failure() 捕获 SSH 连接失败、Action 本身报错,确保任何阶段出问题都能收到通知

前提准备:

  • 服务器已克隆项目代码到 /home/project/nest-service,根目录有 docker-compose.yml
  • 已完成 SSH 免密登录配置
  • 已添加 Secrets:SSH_HOSTSSH_USERSSH_PORTSSH_PRIVATE_KEYWECHAT_WEBHOOK_URL

工作流文件 .github/workflows/deploy.yml

yaml
name: Deploy NestJS Service

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: SSH 连接并执行部署
        uses: appleboy/ssh-action@master # 免密 SSH 远程执行命令
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          port: ${{ secrets.SSH_PORT }}
          script: |
            cd /home/project/nest-service
            git checkout -- .               # 清除本地未提交改动,避免 pull 冲突
            git pull origin main
            docker compose up -d --build    # 重建镜像并后台启动,旧容器自动替换

            # 定义通知函数;$1=字体颜色 $2=结果文字
            # 变量通过下方 env 注入,script 内不能直接用 ${{ secrets.* }}
            notify() {
              curl -s -X POST -H 'Content-Type: application/json' \
                -d "{
                  \"msgtype\": \"markdown\",
                  \"markdown\": {
                    \"content\": \"仓库: **$REPO**\n结果: **<font color=\\\"$1\\\">$2</font>**\"
                  }
                }" \
                "$WEBHOOK_URL"
            }

            if [ $? -eq 0 ]; then
              notify "info" "部署成功 ✓"
            else
              notify "warning" "部署失败 ✗"
              exit 1    # 主动退出非零,让 Action 标记为失败状态
            fi
        env:
          REPO: ${{ github.repository }}
          WEBHOOK_URL: ${{ secrets.WECHAT_WEBHOOK_URL }} # 通过 env 传入远程脚本

      - name: SSH 步骤本身失败时发送通知
        if: failure() # 仅当上面的 step 失败时触发(如 SSH 连接超时、网络问题)
        run: |
          curl -s -X POST -H 'Content-Type: application/json' \
            -d '{
              "msgtype": "markdown",
              "markdown": {
                "content": "GitHub Actions 执行异常,请检查 Actions 日志"
              }
            }' \
            "${{ secrets.WECHAT_WEBHOOK_URL }}"

appleboy/ssh-actionscript 在服务器上执行,无法直接读取 ${{ secrets.* }}(那是 Runner 侧的变量)。应通过 env 字段将需要的值注入为环境变量,再在脚本中用 $变量名 引用。


NestJS 服务部署(推荐方案:CI 构建镜像 + GHCR)

简化方案在服务器上构建镜像,有三个缺陷:服务器需要安装 Node.js 等构建工具;编译消耗生产服务器资源;镜像没有版本标记,出问题无法快速回滚。

业界标准做法分三步:

  1. GitHub Runner 构建 Docker 镜像
  2. 推送到 GitHub Container Registry(GHCR,免费,与仓库权限联动)
  3. SSH 进服务器,只做 docker compose pull + docker compose up -d,不重新编译

服务器上只需要安装 Docker,Node.js 和 pnpm 不需要。每次部署产生一个带 commit SHA 的镜像版本,回滚只需修改 docker-compose.yml 中的镜像 tag。

前提准备:

  • 服务器已安装 Docker
  • 已完成 SSH 免密登录配置
  • 已添加 Secrets:SSH_HOSTSSH_USERSSH_PORTSSH_PRIVATE_KEY
  • 仓库 Settings → Actions → General → Workflow permissions 选择 Read and write permissions(允许推送镜像到 GHCR)

服务器上的 docker-compose.yml(部署前提前放好):

yaml
services:
  app:
    image: ghcr.io/your-org/nest-service:latest # 替换为实际仓库路径
    restart: always
    ports:
      - '3000:3000'
    env_file:
      - .env # 服务器本地的环境变量文件,存放数据库密码等敏感配置

工作流文件 .github/workflows/deploy.yml

yaml
name: Build and Deploy

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: 登录 GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }} # 自动颁发,无需手动创建

      - name: 生成镜像 tag(使用 commit SHA)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=sha,prefix=      # 生成形如 sha-abc1234 的 tag
            type=raw,value=latest # 同时打 latest tag

      - name: 构建并推送镜像
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}

  deploy:
    runs-on: ubuntu-latest
    needs: build # 等镜像推送完成后执行
    steps:
      - name: SSH 拉取镜像并重启服务
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          port: ${{ secrets.SSH_PORT }}
          script: |
            echo "$GHCR_TOKEN" | docker login ghcr.io -u "$GHCR_USER" --password-stdin
            cd /home/project/nest-service
            docker compose pull             # 拉取最新镜像
            docker compose up -d            # 启动新容器(旧容器自动替换)
            docker image prune -f           # 清理旧镜像,防止磁盘积累
        env:
          GHCR_USER: ${{ github.actor }}
          GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}

两种方案对比:

对比项简化方案(服务器构建)推荐方案(CI 构建 + GHCR)
服务器需要安装Node.js、pnpm、git、Docker只需 Docker
构建资源消耗占用生产服务器占用 GitHub Runner(免费)
镜像版本化每次提交对应一个镜像 tag
回滚需要 git revert 再部署修改 tag 后 docker compose up -d
部署速度慢(服务器上编译)快(只拉镜像 + 重启)

GHCR 的镜像权限跟仓库一致:公开仓库镜像公开可拉取,私有仓库镜像需要登录。如果嫌每次传 GITHUB_TOKEN 麻烦,可以在服务器上用 docker login ghcr.io 提前登录一次(用 PAT),之后就不需要在工作流里每次登录了。


pnpm / npm 依赖缓存加速

每次工作流运行都会重新下载依赖,对于 node_modules 上百 MB 的项目会很慢。通过缓存 pnpm store 或 npm cache,可以把安装时间从 1~2 分钟压缩到几秒钟。

actions/setup-node 内置了 cache 参数,直接启用即可,不需要手动配置 actions/cache

yaml
- name: 安装 Node.js
  uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'pnpm' # 自动缓存 pnpm store;npm 项目改为 'npm'

pnpm 项目还需要在这一步之前先安装 pnpm,否则 cache: 'pnpm' 找不到 pnpm 的 store 路径:

yaml
steps:
  - uses: actions/checkout@v4

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

  - name: 安装 Node.js(含缓存)
    uses: actions/setup-node@v4
    with:
      node-version: '20'
      cache: 'pnpm' # pnpm 已安装后才能正确找到 store 路径

  - name: 安装依赖
    run: pnpm install

缓存基于 lock 文件(pnpm-lock.yaml / package-lock.json)的哈希值,lock 文件变动时自动失效并重新下载。


多环境部署策略(dev 分支 → 测试,main 分支 → 生产)

常见的做法是用分支区分环境:main 对应生产服务器,dev 对应测试服务器。同一个工作流文件通过 if 条件和不同的 Secrets 组实现分支路由。

推荐方式:同一个工作流,用 if 区分分支

不同环境的服务器 Secrets 用不同前缀区分(如 PROD_SSH_HOST / DEV_SSH_HOST):

yaml
on:
  push:
    branches:
      - main
      - dev

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: 部署到生产环境
        if: github.ref == 'refs/heads/main' # 只有 main 分支执行
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.PROD_SSH_HOST }}
          username: ${{ secrets.PROD_SSH_USER }}
          key: ${{ secrets.PROD_SSH_PRIVATE_KEY }}
          port: ${{ secrets.PROD_SSH_PORT }}
          script: |
            cd /home/project/nest-service
            git pull origin main
            docker compose up -d --build

      - name: 部署到测试环境
        if: github.ref == 'refs/heads/dev' # 只有 dev 分支执行
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.DEV_SSH_HOST }}
          username: ${{ secrets.DEV_SSH_USER }}
          key: ${{ secrets.DEV_SSH_PRIVATE_KEY }}
          port: ${{ secrets.DEV_SSH_PORT }}
          script: |
            cd /home/project/nest-service-dev
            git pull origin dev
            docker compose up -d --build

对应需要在 GitHub Secrets 中配置两套变量(PROD_*DEV_*),分别指向两台不同的服务器。


GitHub Pages 静态托管(免服务器)

GitHub Pages 可以直接把仓库的静态文件托管为网站,不需要自己的服务器,适合个人博客、文档站点、开源项目主页。每个 GitHub 账号可以有一个 username.github.io 的主站,仓库也可以单独开启 Pages。

开启方式:

  1. 进入仓库 → SettingsPages
  2. Source 选择 GitHub Actions(推荐,通过工作流控制部署)

工作流文件(以 VitePress 为例,其他框架仅修改构建命令和产物路径):

yaml
name: Deploy to GitHub Pages

on:
  push:
    branches:
      - main

# 必须给 GITHUB_TOKEN 写入权限才能推送到 gh-pages
permissions:
  contents: write
  pages: write
  id-token: write

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: pnpm/action-setup@v4
        with:
          version: latest

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'

      - run: pnpm install

      - run: pnpm docs:build # 构建产物输出到 .vitepress/dist/

      - name: 上传产物
        uses: actions/upload-pages-artifact@v3
        with:
          path: .vitepress/dist # 普通 Vue 项目改为 dist

      - name: 部署到 GitHub Pages
        uses: actions/deploy-pages@v4

部署成功后,访问地址为 https://用户名.github.io/仓库名/(私有仓库需要 GitHub Pro 或以上才能使用免费 Pages)。

如果使用自定义域名,在仓库 Settings → Pages → Custom domain 填入域名,并在 DNS 服务商处添加 CNAME 记录指向 用户名.github.io

持续学习,持续成长