Git 裸仓库 + 钩子自动部署:以 Hexo 博客为例

很多人部署个人博客时,会羡慕「本地 git push,服务器自动更新上线」的体验。其实不需要 Jenkins、也不需要 GitHub Actions,只用 Git 自带的 裸仓库(bare repository)钩子(hooks) 就能实现。本文从概念讲清楚,再以 Hexo 博客为例完整实战一遍。

一、什么是 Git 裸仓库

普通仓库(我们平时 git clone 下来的那种)由两部分组成:

  • 工作区(working tree):你能看到、能编辑的源文件;
  • .git 目录:Git 的版本数据库,记录所有提交、分支、对象。

裸仓库只有后者——它没有工作区,整个仓库本身就是 .git 的内容。所以你进到裸仓库里,看不到 index.md_config.yml 这些源文件,只能看到 HEADobjects/refs/hooks/ 这些 Git 内部结构。

裸仓库一般以 .git 结尾命名(如 blog.git),创建命令是:

1
git init --bare blog.git

它的定位是「只负责存储和接收推送的中转站」,专门给多人/多端推送拉取用,因此不需要、也不应该有工作区(有工作区的仓库被 push 时容易产生冲突)。

二、裸仓库 vs 普通仓库 vs Gitea/GitLab

这三者经常被混在一起,其实是不同层次的东西:

维度 普通仓库 裸仓库 Gitea / GitLab
有无工作区 ✅ 有源文件 ❌ 只有 Git 数据库 底层用裸仓库存储
主要用途 本地开发、编辑 接收推送的中转/部署点 完整的代码托管平台
访问方式 本地文件系统 SSH / 文件路径 Web UI + HTTP/SSH
附带功能 无(仅钩子) 网页浏览、权限管理、Issue、PR、CI/CD、Wiki…
资源占用 极低 较高(需常驻服务、数据库)

一句话概括:

  • 普通仓库是你干活的地方;
  • 裸仓库是一个轻量的「服务端仓库」,只做存储和接收;
  • Gitea / GitLab 是在裸仓库之上套了一整套 Web 平台(用户系统、网页界面、CI、协作功能)。它们底层存储的,本质上也是一个个裸仓库。

所以如果你只是想「一个人、一个博客、push 上去就发布」,根本不必装 Gitea/GitLab——它们是重型平台,会常驻进程、占内存(在 serv00 这种限制 512M 内存的免费主机上甚至跑不起来)。一个裸仓库 + 钩子,零额外依赖,足矣。

三、什么是 Git Hooks(钩子)

钩子是 Git 在特定事件发生时自动执行的脚本。它们存放在仓库的 hooks/ 目录里,文件名即事件名。只要文件可执行(chmod +x),Git 在对应时机就会调用它。

钩子分两大类:

客户端钩子(在本地触发)

钩子 触发时机
pre-commit 提交前(常用于代码检查、格式化、跑测试)
commit-msg 校验提交信息格式
pre-push 推送前

服务端钩子(在接收推送的仓库触发)

钩子 触发时机
pre-receive 收到推送、写入引用之前(可整体拒绝)
update 每个分支引用更新前(逐个分支判断)
post-receive 推送完成之后(最常用于自动部署)

我们实现「push 即部署」靠的就是服务端的 post-receive:它在推送成功落地后执行,正适合用来触发构建和发布。

默认情况下 hooks/ 目录里全是 .sample 示例文件,去掉 .sample 后缀并加上可执行权限即可启用。

四、在服务器上搭建裸仓库接收本地推送

假设服务器用户为 git,下面以 SSH 推送为例。

1. 服务器端:创建裸仓库

1
2
3
4
# 登录服务器
mkdir -p ~/repo
cd ~/repo
git init --bare blog.git

执行后 ~/repo/blog.git 里就是裸仓库结构(HEADconfigobjects/refs/hooks/)。

2. 本地:添加远程并推送

1
2
3
4
5
# 在本地 Hexo 项目目录
git remote add deploy ssh://git@服务器IP:端口/~/repo/blog.git

# 首次推送
git push deploy main

端口默认 22 可省略。serv00 等主机的 SSH 端口不是 22,记得带上。能正常 push,说明裸仓库已经能接收推送了。

此时推送只是把代码存进了裸仓库,还不会自动部署——下一步交给钩子。

五、关键问题:裸仓库里看不到源文件,怎么编译发布?

这是最容易卡住的地方。裸仓库没有工作区,目录里全是 Git 对象,自然没法直接 hexo generate

答案是:钩子在执行时,把推送上来的内容「检出(checkout)」到另一个临时工作目录,再在那里编译。

核心命令是给 git checkout 显式指定两个路径:

1
git --git-dir=<裸仓库路径> --work-tree=<临时工作目录> checkout -f <分支>
  • --git-dir:到哪里读 Git 数据(裸仓库);
  • --work-tree:把文件还原到哪个目录(临时工作区)。

这样就「凭空」在工作目录里得到了完整的源文件,后面 npm installhexo generate 就和本地一模一样了。生成的静态文件再复制到 Nginx 的站点目录,就完成了发布。

整个数据流是:

1
2
3
4
5
6
7
本地 git push  →  裸仓库(blog.git)接收  →  触发 post-receive 钩子

checkout 到临时工作目录(拿到源文件)

npm install + hexo generate

把 public/ 复制到 Nginx 站点目录 → 上线

六、实战:Hexo 的 post-receive 钩子

在服务器上编辑裸仓库的钩子文件:

1
nano ~/repo/blog.git/hooks/post-receive

写入以下内容(按自己的路径修改变量):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/bin/bash
set -e

# ===== 可配置变量 =====
REPO="$HOME/repo/blog.git" # 裸仓库路径
WORK_TREE="$HOME/hexo-build" # 临时构建目录(源文件检出到这里)
PUBLISH_DIR="$HOME/www/blog" # 站点发布目录(被 Nginx 托管)
BRANCH="main" # 监听的分支

# ===== 重要:清除 Git 环境变量 =====
# post-receive 运行时 GIT_DIR 已被设为裸仓库,会干扰后续 git 命令,必须清掉
unset GIT_DIR

echo "===> 收到推送,开始部署..."

# 1. 把推送上来的源文件检出到临时工作目录
rm -rf "$WORK_TREE"
mkdir -p "$WORK_TREE"
git --git-dir="$REPO" --work-tree="$WORK_TREE" checkout -f "$BRANCH"

# 2. 进入工作目录,安装依赖并构建
cd "$WORK_TREE"
npm install --production=false
npx hexo clean
npx hexo generate

# 3. 发布:把生成的 public/ 同步到站点目录
mkdir -p "$PUBLISH_DIR"
rm -rf "$PUBLISH_DIR"/*
cp -r "$WORK_TREE/public/." "$PUBLISH_DIR/"

echo "===> 部署完成!"

赋予可执行权限:

1
chmod +x ~/repo/blog.git/hooks/post-receive

完成后,本地每次:

1
git push deploy main

服务器就会自动检出源码、hexo generate、把成品推到站点目录,几秒后博客即更新上线。

几个实战要点

  1. unset GIT_DIR 必不可少。post-receive 运行时环境里已有 GIT_DIR 指向裸仓库,不清掉的话 cd 进工作目录后的 git 命令会全部指向错误位置,导致构建失败。
  2. checkout -f-f 表示强制覆盖工作目录,保证每次都是干净的最新源码。
  3. npm install 别只装生产依赖。Hexo 的渲染器、主题等很多在 devDependencies 里,用 --production=false(或直接 npm install)才能装全。首次较慢,之后有缓存会快。
  4. 复制用 public/. 而不是 public/*,可以连同隐藏文件一起复制。
  5. 站点目录与 Nginx 对应PUBLISH_DIR 必须是 Nginx 配置中 root 指向的目录。
  6. 想看部署日志,可在脚本里把输出重定向到日志文件,或推送时直接观察终端回显(post-receive 的 stdout 会回传给本地终端)。

七、和 GitHub Actions / Gitea 比一比

方案 适合场景 成本
裸仓库 + post-receive 单人、单机部署,想要极简 几乎为零,无常驻进程
GitHub Actions 代码托管在 GitHub,想要云端构建 免费额度内零成本,依赖 GitHub
Gitea / GitLab 团队协作、需要网页/权限/Issue/CI 需常驻服务,吃内存

对个人 Hexo 博客、尤其是部署在 serv00 这类资源受限的免费主机上,裸仓库 + 钩子是性价比最高的方案:没有第三方依赖、不占常驻进程、推送即发布。

小结

  • 裸仓库是没有工作区、只存 Git 数据库的服务端仓库,专门用来接收推送;
  • 它和普通仓库的区别在于「有没有工作区」,和 Gitea/GitLab 的区别在于「是不是一整套平台」;
  • 钩子是 Git 在特定事件自动执行的脚本,post-receive 用于推送后自动部署;
  • 裸仓库虽看不到源文件,但钩子能用 git --work-tree=... checkout 把源码检出到临时目录,再编译发布;
  • 对 Hexo 而言,一段 post-receive 脚本就能实现 git push 即自动 hexo generate 并上线。

把 CI/CD 想得太重,往往会忽略 Git 自带的这套轻巧机制。对个人项目来说,它简单、可靠、零成本。


Git 裸仓库 + 钩子自动部署:以 Hexo 博客为例
http://eevann.cn/2026/06/24/git-bare-repo-hooks-deploy/
作者
月下独白
发布于
2026年6月24日
许可协议