过去写完一篇笔记,需要手动执行编译指令,打开 FTP 软件,再把文件生硬地拖拽到服务器上。现在可以通过 GitHub Actions,实现一套极致的极客工作流:只要在本地执行 git push 把代码推送到 GitHub 的主分支,云端就会自动完成编译,并通过 SSH 悄无声息地将最新博客更新到 VPS 上 。这不仅让我们彻底告别了手动上传的繁琐,更是掌握现代互联网大厂 DevOps 核心工作流的关键一步 。
针对这台小内存 VPS,我们采用了最轻量级的方案:利用 GitHub 免费的高配置服务器进行“重体力”编译,然后直接通过 SCP 协议将纯静态网页传输到 Nginx 目录下,全程不占用 VPS 的一丁点算力。
第一步:将服务器“钥匙”托管给 GitHub#
为了让 GitHub 机器人能够免密登录 VPS 传文件,必须将安全凭证存入仓库的密码箱(Secrets)中。
在私有仓库的 Settings -> Secrets and variables -> Actions 中,依次添加独立变量:
SERVER_IP:VPS 的公网 IP。SERVER_USERNAME:登录用户名(通常为root)。SERVER_PORT:修改过后的高位 SSH 端口。SERVER_SSH_KEY:本地电脑的 SSH 私钥(包含头尾的完整多行文本)。SERVER_SSH_PASSPHRASE:如果生成密钥时设置了保护密码,必须填入此项,否则机器人握手会被无情拒绝。
当然,可以为机器人专门生成一个“无密码”密钥,这样也更符合 DevOps 规范,同时下方的 deploy.yml 要将 passphrase: ${{ secrets.SERVER_SSH_PASSPHRASE }} 这一行删掉
第二步:编写云端流水线剧本 (GitHub Actions)#
在项目根目录新建 .github/workflows/deploy.yml,为 GitHub 的临时服务器下达指令集:
name: Deploy Hugo Blog to VPS
on:
push:
branches:
- main # 监听 main 分支的代码推送
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
# 1. 拉取最新代码 (必须开启 submodules 以拉取主题)
- name: Checkout Code
uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0
# 2. 配置编译环境
- name: Setup Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: "latest"
extended: true
# 3. 执行编译并压缩静态资源
- name: Build Site
run: hugo --minify
# 4. SCP 传输到 VPS
- name: Deploy to VPS
uses: appleboy/scp-action@master
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SERVER_SSH_KEY }}
passphrase: ${{ secrets.SERVER_SSH_PASSPHRASE }} # 密钥保护密码
port: ${{ secrets.SERVER_PORT }}
source: "public/" # 编译产物目录
target: "/opt/blog/public" # VPS 上的目标存放路径
strip_components: 1 # 剥离 public 外壳,直接倾倒内部文件第三步:Nginx 容器的最终接应#
文件顺利抵达 VPS 后,还需要让前端的 Nginx 准确找到它们。
在部署 Nginx 的 docker-compose.yml 中,将宿主机的接收目录挂载到容器内部:
volumes:
# 将 GitHub Actions 传过来的静态文件,挂载到 Nginx 默认读取目录
- /opt/blog/public:/usr/share/nginx/html并在映射的 nginx.conf 中,将根目录精确指向容器内的挂载点:
location / {
root /usr/share/nginx/html; # 必须是容器内的绝对路径
index index.html index.htm;
}执行 docker compose down 与 docker compose up -d 重启容器,大功告成!
🛠️ 避坑指南 (Troubleshooting)#
在打通这条流水线的过程中,我踩过了几个极其经典的工程化“大坑”,特此记录:
- 私钥的密码保护门槛:报错
ssh: this private key is passphrase protected。就是生成密钥时设置了 Passphrase,机器人将无法输入。必须在流水线的 scp 插件参数中显式传递passphrase字段。 - 连字符问题:在 VPS 终端执行重启时报错
Command 'docker-compose' not found。现代 Docker 环境(V2+)已将 compose 集成为内置插件,必须去掉中间的连字符,改用docker compose。 - Nginx 强拦截指令的遮挡:部署成功后依旧显示旧版测试页面。原因在于
nginx.conf中遗留了return 200 '...';拦截指令。Nginx 识别到该指令会直接返回文本,不再读取本地 HTML,必须将其替换为标准的root指令。
至此,整套自动化流程已彻底打通。把重复的劳动外包给机器,把精力留给创作!
补充记录:为 GitHub Actions 申请专属密钥(DevOps规范)#
为了安全隔离风险(防止在GitHub仓库密钥泄露时,波及个人主机),不应该直接将私人电脑的私钥交给机器人,而是为它单独配备一把专用的“无密码”钥匙,出问题就直接删除这个bot的专属密钥就可以了:
- 生成无密码密钥:
在本地执行
ssh-keygen -t ed25519 -C "github-actions-bot"。- 提示输入保存路径时,切记输入新名字(例如
C:\Users\guo05\.ssh\id_ed25519_github_bot)以防覆盖自己原有的主密钥! - 提示设置密码(Passphrase)时,直接连按两次回车跳过,生成无密码私钥以满足自动化无人值守的需求。
- 提示输入保存路径时,切记输入新名字(例如
- 给 VPS 放行:
将生成的公钥(
.pub结尾的文件)内容复制,追加粘贴到 VPS 的/root/.ssh/authorized_keys文件的最下方新起一行。 (建议在公钥的乱码末尾空一格,加上如github-actions-bot的备注,方便辨认。) - 把私钥托付给 GitHub:
将私钥(无后缀的文件)的完整内容复制。去到 GitHub 仓库的
Settings->Secrets and variables->Actions中,新建或覆盖掉原本的SERVER_SSH_KEY机密变量。 - 清理冗余配置:
因为这是一把无锁的钥匙,所以不用再传解密密码:
- 将
.github/workflows/deploy.yml脚本中passphrase: ${{ secrets.SERVER_SSH_PASSPHRASE }}这一行整行删除,防止解密报错。 - 顺手在 GitHub 网页上删掉不再使用的
SERVER_SSH_PASSPHRASESecret,保持环境配置纯净。
- 将










