Git + Webhook 实现网站自动部署: push 代码即上线,告别 FTP 手动上传


还在用 FTP 一个个文件上传网站?改个 CSS 都要等半天,还容易漏传文件覆盖错版本。今天教你用 Git + Webhook 搭建一套自动化部署流水线,本地 push 代码,服务器自动拉取、更新、重启,全程 10 秒内完成。


一、架构原理

本地开发 → Git 提交 → 推送至远程仓库 → Webhook 触发 → 服务器自动拉取 → 执行部署脚本 → 网站更新

用到的工具:

  • Git:版本控制
  • Gitea / GitHub / GitLab:远程仓库(Gitea 可自建,适合私有项目)
  • Webhook:代码推送时触发 HTTP 请求
  • 部署脚本:服务器端执行 git pull + 后续操作

二、服务器端安装 Gitea(自建 Git 仓库)

如果你不想用 GitHub 公开仓库,Gitea 是最佳选择——轻量、开源、一个二进制文件就能跑。

  1. 下载安装
# 创建用户
useradd -r -m -s /bin/bash git

# 下载 Gitea(以 1.21 为例)
wget -O /usr/local/bin/gitea https://dl.gitea.com/gitea/1.21/gitea-1.21-linux-amd64
chmod +x /usr/local/bin/gitea

# 创建目录
mkdir -p /var/lib/gitea/{custom,data,log}
chown -R git:git /var/lib/gitea
chmod -R 750 /var/lib/gitea
  1. 创建 systemd 服务

/etc/systemd/system/gitea.service

[Unit]
Description=Gitea
After=network.target

[Service]
User=git
Group=git
WorkingDirectory=/var/lib/gitea
ExecStart=/usr/local/bin/gitea web --config /var/lib/gitea/app.ini
Restart=always
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea

[Install]
WantedBy=multi-user.target
  1. 启动配置
systemctl enable gitea
systemctl start gitea

浏览器访问 http://your-server:3000,完成初始化配置(数据库用 SQLite 即可,轻量站点够用)。


三、创建仓库并配置 Webhook

  1. 在 Gitea 创建仓库

登录 Gitea → 右上角 + → 新建仓库 → 名称填 mywebsite → 创建。

  1. 添加 Webhook

进入仓库 → 设置 → Web 钩子 → 添加 Web 钩子 → Gitea

目标 URL:http://your-server:9000/hook/deploy

HTTP 方法:POST

密钥:设置一个随机字符串,如 MySecretKey123

触发条件:只勾选 推送事件


四、服务器端部署脚本

  1. 创建 Webhook 接收服务

用 Python 写一个简易的 HTTP 接收器,监听 9000 端口:

/opt/deploy-server.py

#!/usr/bin/env python3
import hmac
import hashlib
import subprocess
from http.server import BaseHTTPRequestHandler, HTTPServer

# 配置
SECRET = b'MySecretKey123'          # 与 Gitea Webhook 密钥一致
REPO_DIR = '/var/www/mywebsite'      # 网站代码目录
BRANCH = 'main'                      # 监听的分支
DEPLOY_SCRIPT = '/opt/deploy.sh'     # 部署脚本路径

class Handler(BaseHTTPRequestHandler):
    def do_POST(self):
        if self.path != '/hook/deploy':
            self.send_error(404)
            return

        content_length = int(self.headers['Content-Length'])
        body = self.rfile.read(content_length)

        # 验证签名
        signature = self.headers.get('X-Gitea-Signature', '')
        expected = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
        if not hmac.compare_digest(signature, expected):
            self.send_error(403)
            return

        # 执行部署
        try:
            result = subprocess.run(
                ['bash', DEPLOY_SCRIPT],
                capture_output=True,
                text=True,
                timeout=60
            )
            print(f"Deploy output: {result.stdout}")
            if result.returncode != 0:
                print(f"Deploy error: {result.stderr}")

            self.send_response(200)
            self.end_headers()
            self.wfile.write(b'OK')
        except Exception as e:
            print(f"Deploy failed: {e}")
            self.send_error(500)

    def log_message(self, format, *args):
        # 简化日志输出
        print(f"[{self.log_date_time_string()}] {format % args}")

if __name__ == '__main__':
    server = HTTPServer(('127.0.0.1', 9000), Handler)
    print('Deploy server running on port 9000...')
    server.serve_forever()
  1. 创建部署脚本

/opt/deploy.sh

#!/bin/bash
set -e

REPO_DIR="/var/www/mywebsite"
LOG_FILE="/var/log/deploy.log"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

echo "[$TIMESTAMP] 开始部署..." >> $LOG_FILE

cd $REPO_DIR

# 拉取最新代码
echo "[$TIMESTAMP] 拉取代码..." >> $LOG_FILE
git fetch origin
git reset --hard origin/main

# 如果是 PHP 项目,可以在这里加 composer 安装
# if [ -f "composer.json" ]; then
#     composer install --no-dev --optimize-autoloader
# fi

# 如果是 Node 项目,构建前端
# if [ -f "package.json" ]; then
#     npm ci
#     npm run build
# fi

# 清理缓存(以 WordPress 为例)
# wp cache flush --allow-root

# 重启相关服务
echo "[$TIMESTAMP] 重启 Nginx..." >> $LOG_FILE
systemctl reload nginx

# 设置正确权限
chown -R www-data:www-data $REPO_DIR
find $REPO_DIR -type f -exec chmod 644 {} \;
find $REPO_DIR -type d -exec chmod 755 {} \;

TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$TIMESTAMP] 部署完成" >> $LOG_FILE

赋予执行权限:

chmod +x /opt/deploy-server.py /opt/deploy.sh
  1. 用 systemd 管理部署服务

/etc/systemd/system/deploy-server.service

[Unit]
Description=Git Auto Deploy Server
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/python3 /opt/deploy-server.py
Restart=always
User=root

[Install]
WantedBy=multi-user.target
systemctl enable deploy-server
systemctl start deploy-server

五、本地开发配置

  1. 初始化本地仓库
cd ~/projects/mywebsite
git init
git add .
git commit -m "Initial commit"
  1. 添加远程仓库
git remote add origin http://your-server:3000/gituser/mywebsite.git
  1. 推送代码
git push -u origin main

此时 Gitea 会触发 Webhook,服务器自动执行部署脚本,网站更新。


六、进阶:多环境部署

生产环境 + 测试环境分离

本地仓库结构:

main 分支 → 生产环境 (www.yourdomain.com)
dev  分支 → 测试环境 (dev.yourdomain.com)

修改部署脚本,根据分支执行不同操作:

#!/bin/bash
set -e

BRANCH=$(git rev-parse --abbrev-ref HEAD)
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

if [ "$BRANCH" = "main" ]; then
    TARGET_DIR="/var/www/production"
    NGINX_CONF="production"
    echo "[$TIMESTAMP] 部署生产环境..."
elif [ "$BRANCH" = "dev" ]; then
    TARGET_DIR="/var/www/staging"
    NGINX_CONF="staging"
    echo "[$TIMESTAMP] 部署测试环境..."
else
    echo "[$TIMESTAMP] 未知分支,跳过部署"
    exit 0
fi

cd $TARGET_DIR
git fetch origin
git reset --hard origin/$BRANCH

# 环境特定操作
systemctl reload nginx
echo "[$TIMESTAMP] $BRANCH 部署完成"

七、安全加固

  1. Webhook 签名验证

上面已经实现了 HMAC-SHA256 签名验证,确保请求来自你的 Gitea。

  1. 限制 Webhook 来源 IP

在防火墙只允许 Gitea 服务器 IP 访问 9000 端口:

iptables -A INPUT -p tcp --dport 9000 -s 127.0.0.1 -j ACCEPT
iptables -A INPUT -p tcp --dport 9000 -j DROP
  1. 使用 HTTPS

生产环境建议用 Nginx 反向代理,给 Gitea 和 Webhook 都加上 HTTPS:

server {
    listen 443 ssl;
    server_name git.yourdomain.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

server {
    listen 443 ssl;
    server_name hook.yourdomain.com;

    location /hook/ {
        proxy_pass http://127.0.0.1:9000;
        proxy_set_header Host $host;
    }
}
  1. 部署日志审计

定期查看部署日志,发现异常及时处理:

tail -f /var/log/deploy.log

八、与 GitHub/GitLab 集成

如果你用 GitHub 而不是自建 Gitea:

GitHub Webhook 配置:

Settings → Webhooks → Add webhook

  • Payload URL: https://hook.yourdomain.com/hook/deploy
  • Content type: application/json
  • Secret: MySecretKey123
  • Which events: Just the push event

修改 Python 接收器验证方式:

GitHub 用 X-Hub-Signature-256 头,算法是 sha256=...

signature = self.headers.get('X-Hub-Signature-256', '').replace('sha256=', '')
expected = hmac.new(SECRET, body, hashlib.sha256).hexdigest()

九、常见问题排查

问题一:Webhook 触发但代码没更新

检查:

  • 部署服务是否运行:systemctl status deploy-server
  • 日志输出:tail /var/log/deploy.log
  • 目录权限:git 用户是否有写权限到 /var/www/mywebsite

问题二:本地 push 失败

git remote -v
# 确认远程地址正确

git push origin main
# 如果提示权限不足,检查 Gitea 用户权限

问题三:Webhook 返回 403

  • 检查密钥是否一致
  • 检查 X-Gitea-Signature 头是否存在
  • 确认时间戳不过期(某些平台会校验时间)

十、完整工作流总结

日常开发流程:

  1. 本地修改代码
  2. git add . && git commit -m "描述"
  3. git push origin main
  4. 等待 10 秒,刷新网站查看效果
  5. 如果出问题,git revert 回滚,再次 push 自动恢复

相比 FTP 上传的优势:

  • 版本可追溯,每次更新都有记录
  • 团队协作不冲突,多人同时开发
  • 回滚秒级完成,出问题立即恢复
  • 自动化部署,专注写代码

总结

Git + Webhook 自动化部署是现代网站运维的标准做法。初期配置需要 30 分钟,但之后每次更新节省的时间和避免的失误,绝对值回票价。建议从个人项目开始练手,熟练后推广到团队所有项目。


Let's Encrypt、ZeroSSL、Google Trust Services 免费证书对比:2026 年 SSL 证书选型与自动续期全攻略

Linux 内核参数调优实战:TCP 连接数、文件句柄、Swap 配置,榨干 VPS 每一滴性能

评 论
更换验证码