mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1597 字
4 分钟
Risuki 博客自动部署记录
2026-06-17

为什么要整理这条部署链路#

博客刚搭起来的时候,最直接的办法是本地执行构建,再把 dist 放到服务器目录里。这个方式容易理解,也适合第一轮验证。

但它有一个明显问题:每次更新文章都要手动构建、手动推送、手动登录服务器同步。步骤一多,就容易忘,出错时也不好判断问题发生在哪一段。

这次我把 Risuki 博客的发布流程整理成了下面这条链路:

本地 push main
GitHub Actions 自动 build
deploy 分支自动更新
阿里云服务器每 10 分钟同步 deploy 分支
Nginx 展示最新静态文件

这篇文章记录这条链路为什么这样设计、具体怎么配置,以及中间遇到的问题。

整体思路#

这个博客是 Astro 静态站,构建后的结果就是一批 HTML、CSS、JS 和图片文件。服务器最终只需要提供这些静态文件。

所以我不希望低配服务器负责构建。更合理的分工是:

GitHub 保存源码
GitHub Actions 负责构建
deploy 分支保存构建产物
阿里云服务器只负责同步和提供静态文件
Nginx 负责对外访问

这样服务器上不需要跑 pnpm install,也不需要维护复杂的 Node.js 构建环境。构建失败就看 GitHub Actions,网站打不开再看 Nginx 和服务器。

GitHub Actions 自动构建#

仓库里新增了 .github/workflows/deploy.yml,核心职责是:

  1. 监听 main 分支。
  2. 安装依赖。
  3. 执行 pnpm build
  4. dist 目录发布到 deploy 分支。

关键流程大概是:

on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: write

这里有两个点比较重要。

第一个是 workflow_dispatch。它表示这个 workflow 支持手动触发。后续如果想重新部署一次,不一定非要再提交代码。

第二个是 contents: write。因为 workflow 要把构建后的 dist 推送到 deploy 分支,所以 GitHub Actions 需要仓库内容写权限。

首次使用前,还需要在 GitHub 仓库里确认:

Settings -> Actions -> General -> Workflow permissions

选择:

Read and write permissions

deploy 分支只放构建产物#

我没有把源码直接放到服务器上构建,而是让 deploy 分支只保存构建后的成品。

发布时在 dist 目录内部初始化 Git:

git -C dist init --initial-branch=deploy
git -C dist add -A
git -C dist commit -m "deploy: publish ${GITHUB_SHA}"
git -C dist push --force origin deploy

这些命令的含义是:

  • git -C dist ...:在 dist 目录里执行 Git 命令,而不是在源码根目录执行。
  • git init --initial-branch=deploy:把 dist 初始化成一个新的 Git 仓库,分支名叫 deploy
  • git add -A:把构建产物全部加入暂存区。
  • git commit ...:生成一次部署提交。
  • git push --force origin deploy:用这次构建产物覆盖远端 deploy 分支。

这里使用 --force 是有意的。deploy 分支不是人工维护的历史分支,而是“当前最新构建产物”的快照。

服务器为什么不能继续 git pull#

一开始我在服务器执行:

git pull origin deploy

结果 Git 提示:

fatal: Need to specify how to reconcile divergent branches.

原因是 GitHub Actions 每次都会强制更新 deploy 分支。服务器本地的 deploy 和远端 deploy 历史可能不再是一条直线。普通 git pull 不知道应该合并、变基还是只允许快进。

既然服务器目录只是构建产物目录,就不应该在这里做 merge。正确做法是直接让服务器目录等于远端 deploy

git fetch origin deploy
git reset --hard origin/deploy

含义:

  • git fetch origin deploy:获取远端 deploy 分支最新状态,但暂时不改当前文件。
  • git reset --hard origin/deploy:把当前目录强制同步成远端 deploy 的内容。

这个目录只保存静态文件,不在服务器上手动改内容,所以这里使用 reset --hard 是合理的。

服务器自动同步脚本#

服务器上的站点目录是:

/var/www/risukio.com/current

我创建了一个更新脚本:

sudo nano /usr/local/bin/update-risukio.sh

脚本内容:

#!/bin/bash
set -e
SITE_DIR="/var/www/risukio.com/current"
LOG_PREFIX="[risukio deploy]"
echo "$LOG_PREFIX start: $(date '+%F %T')"
cd "$SITE_DIR"
/usr/bin/git fetch origin deploy
/usr/bin/git reset --hard origin/deploy
echo "$LOG_PREFIX done: $(date '+%F %T')"

这里每一段都有明确作用:

  • #!/bin/bash:指定用 bash 执行脚本。
  • set -e:脚本中任何命令失败就立刻停止。
  • SITE_DIR=...:保存站点目录路径。
  • LOG_PREFIX=...:统一日志前缀,后续排查时更容易看。
  • cd "$SITE_DIR":进入 Nginx 正在读取的静态站目录。
  • /usr/bin/git fetch ...:拉取远端 deploy 状态。
  • /usr/bin/git reset --hard ...:同步到远端构建产物。

写完后给脚本执行权限:

sudo chmod +x /usr/local/bin/update-risukio.sh

含义:

  • chmod +x:给脚本增加可执行权限。
  • 没有这一步,系统可能不能直接运行这个脚本。

手动测试:

sudo /usr/local/bin/update-risukio.sh

正常输出类似:

[risukio deploy] start: 2026-06-17 16:25:22
From github-blog:Fantuan276/Blog
* branch deploy -> FETCH_HEAD
HEAD is now at 71b2560 deploy: publish 6a962cc06cd30bed96d099a90bde6b1e193cf510
[risukio deploy] done: 2026-06-17 16:25:26

cron 每 10 分钟自动执行#

脚本能手动执行后,再交给 cron 定时执行。

编辑 root 用户的定时任务:

sudo crontab -e

加入:

*/10 * * * * /usr/local/bin/update-risukio.sh >> /var/log/risukio-deploy.log 2>&1

含义:

  • */10 * * * *:每 10 分钟执行一次。
  • /usr/local/bin/update-risukio.sh:执行刚才写好的同步脚本。
  • >> /var/log/risukio-deploy.log:把脚本输出追加到日志文件。
  • 2>&1:把错误输出也写入同一个日志文件。

保存后可以查看当前 crontab:

sudo crontab -l

再查看日志:

tail -n 50 /var/log/risukio-deploy.log

实际验证时,日志里已经出现了:

16:30:01
16:40:01
16:50:01

说明 cron 确实每 10 分钟自动执行一次。

关于流量计费#

我的阿里云实例是按使用流量计费,重点是公网出网流量。

服务器定时执行:

git fetch origin deploy
git reset --hard origin/deploy

主要是服务器从 GitHub 下载内容,这属于入网方向。按当前计费方式,入网通常不计入公网出网费用。

真正消耗出网流量的是用户访问网站时,服务器把页面、脚本、样式、图片返回给浏览器。

而且没有更新时,git fetch 只交换少量 Git 元信息。个人博客每 10 分钟轮询一次,流量压力很小。

现在的日常发布方式#

现在更新博客只需要在本地:

git add .
git commit -m "content: update posts"
git push origin main

之后流程会自动走完:

main 分支更新
GitHub Actions 构建
deploy 分支更新
服务器 cron 同步
Nginx 展示新页面

如果一切正常,以后不需要每次登录服务器。

后续可以继续优化什么#

当前这个方案不是最实时的,因为服务器最多要等 10 分钟才会同步。但它很稳,权限也简单。

后续如果想做到 push 后立刻更新,可以把最后一步改成:

GitHub Actions 构建成功后,通过 SSH 登录服务器执行 update-risukio.sh

那样体验会更接近“真正自动发布”。不过 SSH key、GitHub Secrets、服务器权限都要处理好。对当前个人博客来说,10 分钟 cron 已经足够实用。

小结#

这次部署链路整理下来,核心不是某一条命令,而是把职责分清楚:

本地负责写文章
GitHub 负责保存源码
GitHub Actions 负责构建
deploy 分支负责保存成品
服务器负责同步成品
Nginx 负责提供访问

每一层只做一件事,后面排查问题也会清楚很多。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Risuki 博客自动部署记录
https://risukio.com/posts/risukio-blog-auto-deploy/
作者
Risuki
发布于
2026-06-17
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录