GitHub push/PR合并成功后触发服务器webhook实现CI/CD的方法
触发时机认知纠正
你此前的认知有误。GitHub Actions的事件触发逻辑完全是在对应操作生效后才执行:
push事件:仅当目标分支的代码已经完整推送到GitHub远端仓库、分支引用更新完成后,才会触发工作流运行,不存在“push未生效就先跑Actions”的情况- PR合并场景:只要监听
pull_request事件的closed类型,同时增加github.event.pull_request.merged == true的判断,触发时机就是PR合并动作完全完成、代码已经合入目标分支之后,完全满足你“操作生效后再触发部署”的要求。
符合约束的CI/CD实现方案
以下方案均不依赖DockerHub,仅使用Ubuntu系统自带工具与GitHub Actions,适配你当前Docker Compose部署的Django+gunicorn+PostgreSQL+Traefik栈。
方案1:SSH直连部署(推荐,维护成本最低)
不需要在服务器额外部署常驻webhook服务,直接通过GitHub Actions在触发条件满足后,经SSH连接到服务器执行部署操作,是最简洁的实现方式。
实施步骤
- 服务器侧准备
- 新建独立的部署系统账号,不要直接使用root,给该账号开放项目目录的读写权限、Docker执行权限
- 生成专属的SSH部署密钥对,将公钥写入部署账号的
~/.ssh/authorized_keys文件 - 确认服务器安全组允许SSH端口入站访问
- GitHub仓库配置
进入仓库Settings -> Secrets and variables -> Actions,添加以下加密变量:SERVER_SSH_KEY:部署密钥对应的私钥内容SERVER_HOST:Ubuntu服务器的公网IPSERVER_USER:部署用的系统用户名PROJECT_PATH:项目在服务器上的绝对存储路径,例如/data/django-app
- 工作流编写
在仓库内新建.github/workflows/dev-deploy.yml文件,内容参考如下:
name: Dev Environment Auto Deploy on: push: branches: [ "dev" ] pull_request: branches: [ "dev" ] types: [ closed ] jobs: deploy: runs-on: ubuntu-latest steps: - name: Verify deploy condition id: condition run: | if [[ "${{ github.event_name }}" == "pull_request" && "${{ github.event.pull_request.merged }}" != "true" ]]; then echo "skip=1" >> $GITHUB_OUTPUT else echo "skip=0" >> $GITHUB_OUTPUT fi - name: Execute remote deploy if: steps.condition.outputs.skip == 0 run: | mkdir -p ~/.ssh echo "${{ secrets.SERVER_SSH_KEY }}" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa cat > ~/.ssh/config <<EOF Host target HostName ${{ secrets.SERVER_HOST }} User ${{ secrets.SERVER_USER }} StrictHostKeyChecking no EOF ssh target << 'REMOTE_SCRIPT' cd ${{ secrets.PROJECT_PATH }} git pull origin dev # 仅构建业务镜像,公共镜像无需重新编译 docker compose build --no-cache gunicorn # 执行Django必要变更 docker compose run --rm gunicorn python manage.py migrate --noinput docker compose run --rm gunicorn python manage.py collectstatic --noinput # 重启变更的服务 docker compose up -d # 清理无用镜像释放磁盘空间 docker image prune -f REMOTE_SCRIPT
方案2:自定义Webhook部署(匹配你最初的构思)
如果希望在服务器侧保留统一的部署入口,可以用Ubuntu原生工具实现轻量webhook服务,不需要引入第三方服务。
实施步骤
- 服务器侧配置
- 编写部署脚本
/usr/local/bin/app-deploy.sh,内容和方案1中远程执行的脚本一致,赋予可执行权限 - 用系统自带工具实现简单的HTTP接口:可以用
socat监听端口,收到带正确预共享密钥的POST请求就调用部署脚本,也可以直接通过已部署的Traefik做路由转发,不需要额外装Nginx - 编写systemd服务配置,让webhook进程开机自启、异常自动重启
- 务必给webhook接口加签名/密钥校验,禁止无认证调用,防止被恶意扫描触发部署
- 编写部署脚本
- GitHub Actions配置
触发条件和判断逻辑和方案1完全一致,在确认满足部署条件后,直接在工作流中用curl向服务器的webhook地址发起带密钥的POST请求即可。不要直接使用GitHub仓库原生的webhook功能——原生webhook会在PR创建、更新、关闭等所有相关事件发生时发请求,需要自己写大量过滤逻辑,误触发概率高。
部署注意事项
- PostgreSQL是有状态服务,执行
docker compose up -d时不要加删除卷的参数,避免数据库数据丢失,正常重启流程不会影响存储卷内的数据 - Traefik配置无变更的话,部署过程中不会重启,gunicorn服务重启时Traefik会自动摘除流量,等服务健康检查通过后再导入流量,部署过程基本无感知
- 不需要重新构建PostgreSQL、Traefik等公共基础镜像,仅构建自己的Django+gunicorn业务镜像即可,大幅缩短部署时间
- 如果项目有前端构建、依赖安装等步骤,直接放在服务器侧的部署脚本里执行即可,不需要在Actions中构建后传输文件,流程更简单易维护
内容的提问来源于stack exchange,提问作者Dmitriy Lunev
相关产品推荐
相关产品推荐

