Envoyer 实现零停机部署依赖符号链接切换、独立发布目录和共享路径隔离三者协同;迁移须在 current 切换前完成且向前兼容,.env 和 storage 必须设为共享路径,队列与定时任务需手动重启或更新。

Envoyer 本身不提供“零停机”的魔法开关,它只是把一套已被验证的原子化发布流程封装成了图形界面。真正起作用的是符号链接切换 + 独立发布目录 + 共享路径隔离 —— 这三者缺一不可。如果你在 Envoyer 面板里勾了“Zero Downtime Deployment”但没配对共享目录或迁移仍在新代码加载后执行,那还是会有请求失败。
为什么 current 软链切换能实现零停机
PHP-FPM 处理每个请求时,会根据 Nginx 的 root /home/forge/example.com/current/public 去加载文件。只要 current 指向的是一个完整、已安装依赖、已运行迁移、已生成缓存的目录,切换动作(ln -nfs releases/20260429123456 current)就是纳秒级的。用户不会感知到任何中断,因为旧进程还在处理完手头请求,新请求立刻落到新代码上。
容易踩的坑:
- 没把
.env和storage/设为共享路径 → 新 release 目录读不到配置或上传文件,直接报错 - 在 Envoyer 的“After”阶段才跑
php artisan migrate→ 此时current已切过去,旧代码可能还在执行,而新迁移已改表结构,导致 500 - 启用了“Run migrations in queue”,但队列进程没配置为优雅重启 → 迁移卡在队列里,或者旧 worker 仍用老代码执行任务
php artisan migrate 必须在 current 切换前完成
Envoyer 默认把迁移放在“Deploy”阶段末尾,也就是新目录准备就绪但尚未激活时。这是安全窗口:代码已就位,数据库还没动,旧服务照常运行。你必须确认这个阶段确实执行了迁移,并且迁移本身是向前兼容的。
关键检查点:
- 打开项目设置 → “Deployment Scripts” → 找到 “Before” 或 “Deploy” 区块,确认有
php artisan migrate --force且不在 “After” - 如果迁移含破坏性操作(如删字段、改类型),务必拆成两步:先加字段并兼容旧逻辑,上线后再删;Envoyer 不会帮你做语义分析
- 避免在迁移里写
DB::table(...)->get()加载大量数据 —— 这会阻塞部署,超时后 Envoyer 可能标记失败并回滚
共享路径(shared)不是可选项,是强制项
Envoyer 在服务器上自动创建 /home/forge/example.com/shared,但不会自动把你的 .env 或 storage 放进去。你得手动上传并配置路径映射,否则每次部署都会覆盖掉这些运行时必需的数据。
Laravel Cloud(cloud.laravel.com)。此技能用于处理所有 Laravel Cloud 请求,包括搜索和读取数据。只要任务涉及 Laravel Cloud,即使用此技能。
正确做法:
- 首次部署前,把生产
.env上传到/home/forge/example.com/shared/.env - 在 Envoyer 设置页 → “Shared Files” 里填入:
.env,storage(注意不带斜杠) - 确保
storage目录权限为775,属主是forge:www-data,否则新 release 下的php artisan storage:link会失败 - 不要在 “Deployment Script” 里写
cp -r shared/storage ./storage—— Envoyer 会在每次部署时自动软链,重复操作反而可能破坏结构
队列与定时任务不随 current 自动更新
Nginx 请求走的是 PHP-FPM,它每次请求都重新加载 current 下的代码;但 php artisan queue:work 是长进程,启动后就锁死了当时的代码路径。你切了 current,它还跑在上个 release 里。
解决方案只有两个:
- 在 Envoyer 的 “After” 脚本里加:
sudo supervisorctl reread && sudo supervisorctl update && sudo supervisorctl restart myapp-queue(假设你用 Supervisor 管理) - 或者更稳妥地,在 “Before” 阶段就停掉旧队列:
sudo supervisorctl stop myapp-queue,再在 “After” 启动新队列 - 同理,
php artisan schedule:run如果是用 Cron 调用的,需确认 Cron 条目里路径写的是/home/forge/example.com/current/artisan,而不是固定死的某个 release 目录
最常被忽略的一点:Envoyer 的“健康检查”默认只访问 / 返回 200 就算成功。但它不校验数据库连接、缓存是否生效、队列是否已重启。一次看似成功的部署,可能让后台任务静默失效数小时——直到你发现邮件没发、通知没推、统计漏了数据。










