网站自动化部署实战:用Git Hooks实现推送即上线

引言

作为一个草根站长,你可能经历过这样的场景:写完代码、改好模板,上传到服务器需要先 FTP 连接、拖拽文件、刷新页面看效果,整个过程繁琐且容易出错。如果有多个网站要维护,那简直就是噩梦。

今天这篇文章将带你彻底告别手动部署。通过 Git Hooks,实现"本地推送代码 → 服务器自动拉取并部署"的一键流程。整个过程不需要任何第三方 CI/CD 平台,完全免费且安全可控。

为什么是 Git Hooks?

先说说为什么选择 Git Hooks 而不是其他方案。

Jenkins / GitHub Actions 功能强大但过于笨重。对于一个个人网站来说,搭建 Jenkins 需要 Java 运行环境,内存占用至少 512MB,对于小 VPS 来说是沉重负担。GitHub Actions 虽然免费,但需要将代码托管在 GitHub 上,对于私有项目不太合适。

rsync 手动同步 每次都需要手动执行命令和输入密码,不灵活。

Git Hooks 是 Git 自带的钩子机制,当特定事件发生时自动触发脚本。我们利用服务端的 post-receive 钩子,在接收到推送后自动执行部署。优势很明确:

  • 零额外依赖,Git 本身就支持
  • 资源占用极低
  • 部署速度快,通常 2-5 秒完成
  • 支持回滚,每次部署都有 Git 记录

完整部署方案

第一步:服务器端准备

首先在服务器上创建一个裸仓库(bare repository),裸仓库不包含工作目录文件,只存储 Git 版本历史,适合作为远程中心仓库。

# 在服务器上创建裸仓库
mkdir -p /var/git-repos
cd /var/git-repos
git init --bare mysite.git

这里的 --bare 参数很关键。普通 git init 会创建带工作目录的仓库,而裸仓库只包含 .git 目录的内容,不包含实际文件。服务端的推送目标必须是裸仓库。

第二步:配置 Git 用户

为避免权限问题,确保 Git 操作用户对网站目录有写入权限。如果你的网站运行在 Nginx 下,通常是 www-data 用户:

# 查看 Nginx 运行用户
ps aux | grep nginx | grep -v grep

# 将裸仓库所有权设置为该用户
chown -R www-data:www-data /var/git-repos/mysite.git

第三步:编写 post-receive 钩子

进入裸仓库的 hooks 目录,创建 post-receive 脚本:

cd /var/git-repos/mysite.git/hooks
cat > post-receive << 'EOF'
#!/bin/bash

# 部署目标目录
TARGET="/var/www/mysite"
# 临时工作目录
WORK_TREE="/tmp/mysite_deploy_$$"

echo "=== 开始自动部署 ==="
echo "时间: $(date)"

# 创建临时目录并检出代码
mkdir -p "$WORK_TREE"
git --work-tree="$WORK_TREE" --git-dir=/var/git-repos/mysite.git checkout -f main

# 同步到目标目录(排除 .git 和不需要的文件)
rsync -av --delete \
    --exclude='.env' \
    --exclude='.user.ini' \
    --exclude='node_modules' \
    --exclude='.git' \
    "$WORK_TREE/" "$TARGET/"

# 清理临时目录
rm -rf "$WORK_TREE"

# 设置权限
chown -R www-data:www-data "$TARGET"

# 重启 PHP-FPM(如果需要清除 opcache)
systemctl reload php8.1-fpm 2>/dev/null || service php8.1-fpm reload 2>/dev/null

echo "=== 部署完成 ==="
EOF

# 赋予执行权限
chmod +x post-receive

这个脚本的核心逻辑是:

  1. 将推送的代码检出到一个临时目录
  2. 使用 rsync 同步到网站实际目录
  3. 排除不应覆盖的配置文件(如 .env
  4. 重载 PHP-FPM 以清除 OPCache

注意 $$ 是当前进程 PID,用来创建唯一临时目录,避免并发部署时冲突。

第四步:配置 SSH 免密登录

从本地推送到服务器需要 SSH 认证。配置 SSH Key 免密登录:

# 在本地机器上生成 SSH Key(如果没有的话)
ssh-keygen -t ed25519 -C "deploy@mysite"

# 将公钥复制到服务器
ssh-copy-id -i ~/.ssh/id_ed25519.pub root@你的服务器IP

如果服务器 SSH 端口不是默认的 22,需要指定端口:ssh-copy-id -p 2222 root@IP

第五步:本地仓库配置

在本地项目目录中添加远程仓库并推送:

# 在本地项目目录执行
cd /path/to/your/project

# 添加服务器裸仓库为远程仓库
git remote add production root@你的服务器IP:/var/git-repos/mysite.git

# 确认添加成功
git remote -v

# 推送到生产服务器
git push production main

推送完成后,服务器端的 post-receive 钩子会自动触发,你可以在终端看到部署日志。

进阶技巧

多分支环境

如果你有测试环境和生产环境,可以按分支区分部署目标:

#!/bin/bash
# post-receive 改进版

while read oldrev newrev refname; do
    branch=$(echo $refname | sed 's/refs\/heads\///')
    
    case "$branch" in
        main)
            TARGET="/var/www/mysite_prod"
            ;;
        staging)
            TARGET="/var/www/mysite_staging"
            ;;
        *)
            echo "分支 $branch 未配置自动部署,跳过"
            exit 0
            ;;
    esac
    
    # 部署逻辑同上...
done

部署前检查

在同步文件前增加验证步骤,防止部署损坏的代码:

# 在 rsync 之前加入
if [ ! -f "$WORK_TREE/index.php" ]; then
    echo "错误:未找到 index.php,部署中断"
    rm -rf "$WORK_TREE"
    exit 1
fi

部署通知

部署完成后发送通知,比如用企业微信机器人或 Telegram Bot:

# 发送部署通知
curl -s -X POST "https://api.telegram.org/bot<TOKEN>/sendMessage" \
    -d "chat_id=<CHAT_ID>" \
    -d "text=✅ 网站部署完成
时间: $(date)
分支: $branch
提交: $(git log -1 --pretty=format:'%h %s')" \
    > /dev/null 2>&1

常见问题排查

问题1:推送时提示权限拒绝

检查服务器上的裸仓库权限和 SSH 配置。确保裸仓库的所有者是执行 Git 操作的用户。可以在服务器上执行 sudo -u www-data git --version 确认该用户是否可以执行 Git。

问题2:部署后网站报 500 错误

通常是文件权限问题。确保所有文件和目录所有者是 Web 服务器用户(如 www-data),目录权限 755,文件权限 644。

问题3:修改不生效

可能是 OPCache 缓存了旧代码。在钩子脚本中加入 systemctl reload php-fpm 或使用 opcache_reset() 函数。将以下代码保存为 clear_cache.php 并在钩子中通过 curl 访问:

<?php opcache_reset(); echo "OK";

问题4:Git 提示 "denyCurrentBranch" 错误

你可能是用了非裸仓库作为推送目标。服务器端仓库必须是裸仓库(git init --bare),不能是普通仓库。

.gitignore 配置清单

部署时有些文件绝对不能覆盖服务器上的,有些文件根本不需要传上去。一份合理的 .gitignore 是自动部署的基础:

# 环境配置文件
.env
.env.local
.env.production

# 上传目录(用户上传的图片、附件)
/usr/uploads/
/wp-content/uploads/
/content/uploads/

# 缓存和临时文件
/cache/
/tmp/
/runtime/

# 依赖目录(服务器上单独安装)
node_modules/
vendor/

# 日志文件
*.log
/logs/

# IDE 配置
.idea/
.vscode/
*.swp
*.swo

这里我想重点提醒的是上传目录的处理。很多新手站长第一次用 Git 部署,直接把整个网站目录推上去,结果服务器上用户上传的图片全部丢失。解决方案就是在 .gitignore 中排除上传目录,同时配合 rsync 的 --delete 选项时做好保护。

数据库变更如何处理

代码部署解决了,但数据库变更怎么办?这可能是自动部署中最棘手的问题。我的做法分两种情况:

方案一:手动执行 SQL(推荐)

对于个人网站来说,数据库变更不频繁。每次需要改表结构时,直接在服务器上执行 SQL。这种方式最简单,也最安全——你清楚地知道每次改了什么:

# 在服务器上执行
mysql -u username -p database_name < migration.sql

方案二:部署钩子自动迁移

如果你需要更自动化的方案,可以在 post-receive 钩子中执行 SQL 迁移文件:

# 在 post-receive 钩子中追加
if [ -f "$WORK_TREE/migrations/latest.sql" ]; then
    echo "=== 执行数据库迁移 ==="
    mysql -u username -p"$DB_PASSWORD" database_name < "$WORK_TREE/migrations/latest.sql"
fi

但这里有个安全风险——数据库密码会以明文形式写在钩子脚本中。建议将密码存放在环境变量或独立配置文件中,并设置严格的权限:chmod 600 /etc/deploy/db.conf

实战案例:Typecho 博客的完整部署流

以我自己的 Typecho 博客为例,完整的部署流程如下:

  1. 主题开发:在本地修改 Handsome 主题的模板和 CSS
  2. 本地测试php -S localhost:8080 启动内置服务器预览效果
  3. 提交代码git add . && git commit -m "优化移动端导航栏样式"
  4. 推送部署git push production main
  5. 自动完成:服务器自动检出代码 → rsync 同步到 /var/www/blog → 重载 PHP-FPM → 发送 Telegram 通知

整个过程从提交到上线不超过 10 秒。更重要的是,如果发现新版本有问题,回滚只需一条命令:

git push production +HEAD~1:main

这里的 + 表示强制推送,HEAD~1 是上一个提交。服务器端钩子会自动部署回滚后的版本,比任何手动恢复都快。

安全注意事项

自动部署虽然方便,但也引入了新的安全风险。以下几点必须注意:

SSH 密钥安全:部署专用的 SSH 密钥对应该与日常使用的密钥分开。如果部署密钥泄露,攻击者可以推送恶意代码到你的服务器。建议添加 command 限制:

在服务器的 ~/.ssh/authorized_keys 中:

command="/usr/bin/git-shell -c \"$SSH_ORIGINAL_COMMAND\"",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 AAAAC3...

git-shell 是 Git 自带的受限 Shell,只允许执行 Git 相关命令,无法登录系统执行其他操作。

钩子脚本审计post-receive 钩子会以仓库所有者的身份执行。确保仓库所有者权限最小化——不要用 root 用户,使用专门的 www-datadeploy 用户。

避免敏感信息入仓:数据库密码、API 密钥、支付密钥等绝对不要提交到 Git 仓库。使用 .env 文件管理并在 .gitignore 中排除。

总结

这套基于 Git Hooks 的部署方案我已经在生产环境稳定运行了 3 年多,管理着 10 多个网站。它最大的优势是简单可靠——没有额外依赖、没有复杂的配置文件、出问题时排查路径清晰。

对于草根站长来说,自动化部署不是锦上添花,而是效率倍增器。写完代码一键推送,5 秒后刷新网站就能看到效果,这种即时反馈会极大提升开发和维护效率。

如果你还在用 FTP 手动上传文件,强烈建议花半小时搭建这套流程。相信我,一旦用上你就回不去了。

延伸阅读

如果你觉得这个方案太基础,还可以进一步了解:

  • Capistrano:Ruby 生态的经典部署工具
  • Deployer:PHP 生态的专业部署工具,支持多阶段部署和回滚
  • GitHub Webhooks:配合 GitHub 实现真正的推送即部署,无需服务端仓库

但说实话,对于 99% 的个人站长来说,本文的 Git Hooks 方案已经绰绰有余了。

Last modification:July 12th, 2026 at 08:01 pm

Leave a Comment