本文目录导读:

针对 PHP 项目的持续部署(CD),实现“自动上线”的核心思路是:代码提交到指定分支(如 main 或 release)后,触发自动化流程,将代码部署到生产环境并生效。
下面我将介绍几种从简单到复杂的实现方案,你可以根据项目规模、团队构成和资源情况进行选择。
核心流程概览
无论哪种方案,通常都包含以下步骤:
- 代码推送:开发者将代码 Push 到远程仓库(如 GitHub, GitLab, Gitee)。
- 触发流程:仓库的 Webhook 通知 CI/CD 系统有新的提交。
- CI阶段(可选但推荐):自动运行测试(单元测试、lint检查)、代码安全检查、构建产物(如编译Sass/JS、清理缓存)。
- CD阶段:将代码部署到目标服务器(生产环境)。
- 生效:重启 PHP-FPM(如果是 CLI 模式常驻进程)或等待下次请求加载(传统模式)。
使用成熟的CI/CD平台(推荐)
这是目前最主流、最稳定的方式,常用的平台有:
- GitHub Actions (GitHub 项目首选)
- GitLab CI/CD (GitLab 项目首选)
- Jenkins (老牌、自托管、高度自定义)
- Drone CI (轻量级、容器化)
- 阿里云/腾讯云 CodePipeline (云厂商集成)
示例:使用 GitHub Actions 部署到 VPS(传统FPM模式)
场景:代码托管在 GitHub,生产环境是 Ubuntu VPS Web 服务器(Nginx + PHP-FPM)。
在服务器上配置 SSH 免密登录
- 在 GitHub 仓库的
Settings -> Secrets and variables -> Actions中添加:SERVER_IP:服务器 IPSERVER_USER:SSH 用户名(如deploy)SSH_PRIVATE_KEY:服务器的部署专用私钥(不要用主账号的!)
在仓库根目录创建 .github/workflows/deploy.yml 文件
name: Deploy to Production
# 1. 触发条件:推送到 main 分支时
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
# 2. 检出代码
- name: Checkout code
uses: actions/checkout@v4
# 3. (可选)设置 PHP 环境并运行测试
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
- name: Install dependencies
run: composer install --no-dev --optimize-autoloader
- name: Run tests
run: php artisan test # 或其他测试命令
# 4. 部署到服务器
- name: Deploy to VPS
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
# 注意:这里使用 -o StrictHostKeyChecking=no 仅用于演示,生产环境请配置known_hosts
# script 中可执行 rsync 同步,或 git pull
script: |
cd /var/www/your-project
git pull origin main
composer install --no-dev --optimize-autoloader
php artisan migrate --force # 数据库迁移,需谨慎
php artisan config:cache
php artisan route:cache
php artisan view:cache
sudo systemctl reload php8.2-fpm # 重启FPM以清除OPcache
这个 YAML 文件做了什么?
- 当
main分支有新的 Push 时,自动启动 Job。 - 在 GitHub 的虚拟机中检出代码,安装依赖,运行测试。
- 通过 SSH 连接到你的生产服务器。
- 在服务器上执行
git pull拉取最新代码,执行 Laravel 的优化命令,最后重启 PHP-FPM。
自建 Shell 脚本 + Git Hooks / Webhook
适用于小型项目、内网环境或高度自定义的场景。
工具:rsync 或 git pull + systemctl
场景:你有一台部署服务器(或与Web服务器同机),一个私有的 Git 仓库(如 Gitea)。
编写部署脚本 deploy.sh
这个脚本放在你的服务器上。
#!/bin/bash # deploy.sh PROJECT_DIR="/var/www/my-php-app" BRANCH="main" PHP_FPM_SERVICE="php8.2-fpm" echo "=== Starting Deployment ===" cd $PROJECT_DIR # 1. 拉取最新代码 git fetch origin git reset --hard origin/$BRANCH # 2. 更新依赖 composer install --no-dev --optimize-autoloader # 3. 运行数据库迁移 (谨慎!) php artisan migrate --force # 4. 优化框架 php artisan optimize # 5. 重启 PHP-FPM 以清除 OPcache sudo systemctl reload $PHP_FPM_SERVICE echo "=== Deployment Complete ==="
设置 Git Server-Side Hook
在你的 Git 服务器上,找到仓库的 hooks 目录,编辑 post-receive 文件(如果没有则创建)。
#!/bin/bash
# post-receive
while read oldrev newrev refname
do
branch=$(git rev-parse --symbolic --abbrev-ref $refname)
# 仅当 main 分支被推送时触发
if [ "$branch" == "main" ]; then
echo "Branch $branch received. Triggering deployment..."
# 调用远程服务器上的部署脚本
ssh deploy-user@your-server-ip "/var/www/my-php-app/deploy.sh"
fi
done
配置 SSH 互信
需要在 Git 服务器与 Web 服务器之间配置 SSH 无密码登录,让 post-receive hook 可以远程执行命令。
优点:完全可控,无第三方依赖。 缺点:需要自行维护脚本和异常处理,回滚等机制需要自己实现。
基于容器化部署(Docker)
如果项目已经容器化,CD会变得非常简单和标准化。
流程:提交代码 -> 构建 Docker Image -> 推送镜像到仓库 -> 在生产环境拉取并重启容器。
准备 Dockerfile
FROM php:8.2-fpm-alpine RUN docker-php-ext-install pdo_mysql opcache WORKDIR /var/www/html COPY . . RUN composer install --no-dev --optimize-autoloader EXPOSE 9000 CMD ["php-fpm"]
使用 GitHub Actions 构建和部署
# 省略了测试步骤...
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker Image
run: docker build -t my-registry/my-php-app:latest .
- name: Push to Registry
run: |
docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PAT }} my-registry.com
docker push my-registry/my-php-app:latest
- name: Deploy on Server
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
docker pull my-registry/my-php-app:latest
docker stop my-php-container || true
docker rm my-php-container || true
docker run -d --name my-php-container -p 9000:9000 my-registry/my-php-app:latest
# 重新加载 Nginx 或反向代理配置
优点:环境完全一致,回滚只需重新部署旧版本的 Image。 缺点:需要学习 Docker,增加了一点基础设施复杂度。
PHP 特定注意事项
-
OPcache 问题:这是 PHP 最大的坑,PHP-FPM 模式下,OPcache 会缓存 PHP 文件的字节码。一定要在部署后重启 PHP-FPM(
systemctl reload php-fpm),否则用户访问到的还是旧代码。- 替代方案:设置
opcache.revalidate_freq = 0并利用opcache_reset()函数,但重启 FPM 是最安全的方式。
- 替代方案:设置
-
数据库迁移:
- 向前迁移:
php artisan migrate --force(Laravel)或phinx migrate。 - 向后回滚:必须有对应的回滚脚本,并集成到你的 CD 流程中,手动回滚数据库是非常危险的。
- 向前迁移:
-
.env 配置文件:
- 绝对不要将
.env文件纳入版本控制。 - 可以维护一个
.env.example,并通过 CI/CD 系统在部署时动态生成.env文件(使用仓库的 Secrets 或环境的变量工具)。 - 更好的做法:将配置文件放在服务器上的
/etc/your-app/目录,部署时通过符号链接引用。
- 绝对不要将
-
文件权限:
- CI/CD 执行用户通常不是
www-data,部署后可能需要重置storage/或uploads/目录的权限。 - 方案:使用
ACL或设置 umask,或在部署脚本最后执行chown -R www-data:www-data storage/ bootstrap/cache/。
- CI/CD 执行用户通常不是
-
静态资源:
- 如果使用了 Vite 或 Webpack,建议在 CI 系统中构建静态资源(
npm run build),避免在生产服务器上安装 Node.js 构建。
- 如果使用了 Vite 或 Webpack,建议在 CI 系统中构建静态资源(
总结与最佳实践
| 场景 | 推荐方案 | 关键点 |
|---|---|---|
| 小团队,单一VPS | GitHub Actions + SSH | 简单、门槛低,注意 .env 和 OPcache,务必使用 Secrets 管理敏感信息。 |
| 多服务器,高并发 | Docker + K8s / Docker Swarm | 标准化、弹性伸缩、滚动更新,构建镜像时包含依赖,部署只拉取镜像。 |
| 内网环境,安全要求高 | GitLab CI/CD | 一套工具管理代码和部署,安全审计功能完善。 |
| 快速原型,不想折腾 | Deployer (PHP 部署工具) | Deployer 是一个专为 PHP 设计的部署工具,可以用 dep deploy 一条命令完成复杂流程,支持 Laravel、Symfony 等,它内部使用 rsync 或 git。 |
落地建议:
- 先确定触发方式:是 Push 到
main就自动上线,还是手动点击按钮上线?对于生产环境,强烈推荐手动点击按钮或 Pull Request 合并时触发,避免未测试的代码直接上线。 - 小步快跑,逐步完善:不要试图一步到位,先从最简单的
SSH + git pull开始,然后再加上测试、告警、回滚等功能。 - 配置回滚方案:CD最重要的不仅仅是“上去”,更是“下来”,确保部署脚本支持回滚。
- 简单回滚:在服务器上保留最近几个版本的目录(如
/releases/v1,/releases/v2),通过符号链接current指向最新版本。 - 回滚脚本:
rm current && ln -s releases/v1 current && systemctl reload php-fpm。
- 简单回滚:在服务器上保留最近几个版本的目录(如
- 监控与告警:部署完成后,自动运行健康检查(
curl http://your-site.com/health),如果不通过,自动触发回滚并通知团队。
从你的问题来看,如果项目不大、团队不大,直接使用 GitHub Actions + SSH 是目前比较实用的切入点,上手快,问题也解决得比较彻底。