Forge默认部署不是零停机,因其直接在current目录执行git pull、composer install和migrate,导致代码/依赖/数据库实时变更,引发Class not found、500错误或队列执行旧逻辑;而Envoyer通过独立release目录、迁移前置、符号链接原子切换及共享路径隔离实现零停机。

Forge 默认部署为什么不是零停机
因为 Forge 没有版本化发布目录,也不做原子切换。它直接在 current 目录里执行 git pull、composer install 和 php artisan migrate —— 这些操作会实时修改正在服务请求的代码和依赖。一旦 composer install 正在写 vendor/,PHP-FPM 就可能加载到一半的类文件;或者迁移删了字段,而旧请求还在用老模型插入数据,立刻报 SQLSTATE[HY000]: General error 或 Call to undefined method。
常见表现包括:
- 页面偶发
Class not found(autoloader 缓存未更新或 vendor 写入中断) - 表单提交 500(新迁移已删字段,但旧代码仍在处理请求)
- 队列任务持续执行旧逻辑(worker 进程没重启,仍加载旧 release 路径)
Envoyer 的 zero-downtime 是怎么生效的
Envoyer 不是靠“开关”实现零停机,而是强制你用一套确定性流程:每次部署生成独立的 releases/20260514123456 目录,完整跑完 composer install、php artisan migrate --force、php artisan config:cache 后,再用 ln -nfs releases/20260514123456 current 切换软链。这个 ln 命令耗时纳秒级,Nginx 下次请求就自然落到新目录,旧进程继续处理完手头请求。
但前提是:
-
.env和storage/必须设为共享路径,否则新 release 读不到配置或上传文件 -
php artisan migrate必须放在 “Deploy” 阶段(即切换前),不能放 “After” - 迁移必须向前兼容:比如要删字段,得先上线兼容新旧结构的代码,再单独部署删字段迁移
- 队列进程需手动重启或配置为优雅退出,否则仍执行旧代码
在 Forge 上模拟 Envoyer 风格部署的关键步骤
Forge 允许你完全重写部署脚本,只要手动补上 Envoyer 那套结构就行。核心不是工具,是动作顺序。
你需要在 Forge 的「Deployment Script」里写类似这样的逻辑:
- 生成带时间戳的 release 目录:
RELEASE=$(date +"%Y%m%d%H%M%S") && mkdir -p releases/$RELEASE - 克隆代码进新目录:
git clone --depth=1 --branch=main https://github.com/you/app.git releases/$RELEASE - 复制共享文件:
cp /home/forge/example.com/shared/.env releases/$RELEASE/和cp -r /home/forge/example.com/shared/storage releases/$RELEASE/ - 进新目录装依赖、跑迁移、清缓存:
cd releases/$RELEASE && $PHP_BINARY composer install --no-dev --optimize-autoloader && $PHP_BINARY artisan migrate --force && $PHP_BINARY artisan config:clear && $PHP_BINARY artisan config:cache - 最后原子切换:
ln -nfs releases/$RELEASE /home/forge/example.com/current - 软链 storage:
$PHP_BINARY artisan storage:link(确保 public/storage 指向新 release 的 storage/app) - 重启队列:
sudo supervisorctl restart example-worker:
注意:$PHP_BINARY 是 Forge 注入的正确 PHP 路径,别硬写 php;artisan migrate --force 漏掉 --force 会在生产环境静默跳过。
最容易被忽略的三个 runtime 细节
哪怕脚本全对,以下三点不检查,零停机也会在上线后几小时内崩掉:
-
bootstrap/cache/config.php和bootstrap/cache/routes-v7.php是硬编码绝对路径的 PHP 文件,必须在新current目录下重新生成,不能复用旧缓存 -
storage/framework/views/里的编译 Blade 模板也含路径信息,php artisan view:clear+php artisan view:cache必须在切换后立即执行 - Supervisor 管理的
php artisan schedule:run进程不会自动 reload,得加sudo supervisorctl reread && sudo supervisorctl update && sudo supervisorctl restart example-scheduler:
这些不是“部署完就完事”的环节,而是每次 current 切换后必须触发的 runtime 同步动作。漏掉任意一个,都可能让新功能在页面上显示,却在队列或定时任务里彻底失效。


















