最可控的自动更新方式是 git pull + php artisan migrate 配合严格部署脚本:校验分支、--ff-only 拉取、composer install --no-dev、迁移前检查状态、清缓存、Webhook 双重鉴权、迁移超时与回滚、常驻进程显式重启。

用 git pull + php artisan migrate 是最可控的自动更新方式
Laravel 本身没有内置“自动更新”机制,所谓自动部署,本质是把 Git 拉取、依赖安装、迁移执行、缓存刷新这些手动操作脚本化。直接跑 composer install 或 php artisan migrate --force 在生产环境极容易出问题——比如迁移失败卡住、缓存未清导致路由错乱、队列监听器还在用旧代码。
推荐做法是写一个轻量部署脚本(如 deploy.sh),按严格顺序执行:
- 检查当前分支是否为
main或production,防止误拉测试分支 - 运行
git pull origin main --ff-only,加--ff-only避免意外 merge 提交污染历史 -
composer install --no-dev --optimize-autoloader,跳过开发依赖,生成高效 autoloader -
php artisan migrate --force前先加判断:用php artisan migrate:status --json检查是否有未执行迁移,避免重复跑或跳过 - 最后依次执行
php artisan config:clear、php artisan cache:clear、php artisan view:clear
Webhook 触发部署时,必须限制来源 IP 并校验签名
GitHub/GitLab 的 Webhook 看似方便,但裸露的 /webhook/deploy 接口一旦被扫描到,攻击者就能随意触发你的生产环境变更。别信“加个 token 参数就安全”这种说法——HTTP 请求参数容易被重放、日志里也可能泄露。
正确做法是利用平台提供的签名机制(如 GitHub 的 X-Hub-Signature-256)在 PHP 脚本里验证:
- 从
$_SERVER['HTTP_X_HUB_SIGNATURE_256']取签名头 - 用你配置在仓库里的 secret,对原始 payload 做
hash_hmac('sha256', $payload, $secret) - 用
hash_equals()对比,防止时序攻击 - 验证失败直接
http_response_code(401); exit;,不进后续逻辑
同时 Nginx/Apache 层应限制该路由只允许 GitHub 官方 IP 段访问(GitLab 同理),双重兜底。
php artisan migrate 在自动流程里必须设超时和回滚预案
线上迁移卡住最常见原因不是语法错误,而是锁表、大数据量重索引、或跨版本字段类型变更(比如从 VARCHAR(191) 改成 TEXT)。这时候如果脚本没设超时,整个部署就挂在那里,新旧代码混跑,用户请求开始报错。
实操建议:
- 给迁移命令加
timeout 300(5 分钟),超时后终止并记录日志,如:timeout 300 php artisan migrate --force 2>&1 - 关键迁移(如改主键、删字段)单独拆成独立文件,命名含
_critical,并在部署脚本里人工确认开关 - 绝不自动执行
php artisan migrate:fresh或php artisan migrate:reset,这类命令在生产环境等同于删库 - 每次部署前备份数据库结构(
mysqldump --no-data)和最新迁移文件快照,出事能快速切回
队列、Horizon、WebSocket 进程不会自动重启,得单独处理
很多人跑完部署脚本发现“新功能没生效”,查半天才发现 php artisan queue:work 还在用旧代码内存镜像;或者 Horizon 控制台显示的仍是老版本号;甚至 Laravel Echo Server 的 WebSocket 连接持续返回旧事件格式。
这不是缓存问题,是常驻进程没更新。必须显式干预:
- 部署脚本末尾加
php artisan queue:restart,让正在工作的进程优雅退出(注意:它只发信号,不等退出完成) - Horizon 要配合 Supervisor 使用:修改 Supervisor 的
program配置,把autorestart=true改成autorestart=unexpected,再用supervisorctl reread && supervisorctl update && supervisorctl restart horizon - 如果用了
laravel-websockets,需调用其 API 发送POST /api/v1/restart(需带X-Socket-ID和 token) - 所有守护进程重启后,建议用
ps aux | grep artisan确认旧进程已消失
漏掉这一环,自动更新就只是“前端看起来变了”,实际业务逻辑仍在旧路径上跑。这点最容易被忽略,因为错误不立刻报,而是慢慢积累成数据不一致。


















