在经历了前几个阶段的“毛坯房装修”和环境搭建后,终于迎来了工程化实践的重头戏:打通 CI/CD 流水线 。
过去写完一篇笔记,我们需要手动执行编译指令,打开 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指令。
至此,整套自动化流程已彻底打通。把重复的劳动外包给机器,把宝贵的精力留给创作!










