生产环境必须用 composer install --no-dev,否则会安装 phpunit、laravel/pint 等 dev 包,导致体积膨胀、安全暴露(如调试端点)、行为污染(如 autoload 偏移);composer.lock 必须与 composer.json 同时提交且语义匹配,否则 install 退化为 update,引发 Class not found、版本不一致、缓存失效等问题。

生产环境必须用 composer install --no-dev,且 composer.lock 必须与 composer.json 同时提交、语义匹配——否则构建产物必然不一致。
为什么 composer install 在生产环境不能省略 --no-dev
不加 --no-dev 会导致 require-dev 中的包(如 phpunit/phpunit、laravel/pint)也被安装,带来三类实际风险:
- 体积膨胀:几十 MB 的无用代码进入生产容器,拉长镜像构建和部署时间
- 安全暴露:开发工具可能带 Web UI 或调试端点(如
symfony/var-dumper的dump()输出),线上被意外触发 - 行为污染:某些 dev 包会修改 autoloader 行为或注册全局钩子,导致运行时逻辑偏移(例如日志驱动被覆盖)
CI/CD 流水线中应强制写死该标志,避免靠文档或人工记忆。
composer.lock 文件缺失或过期的典型故障现象
当 composer.lock 未提交、被 .gitignore 拦截、或长期未更新时,composer install 实际退化为 composer update 行为,引发:
-
Class not found:本地装的是monolog/monolog v3.5.0,CI 装出v3.6.0,后者删了某个接口 -
json_encode() returns false:因ext-json版本浮动,PHP 8.1 下某依赖要求json >= 8.1.5,但 CI 的 PHP 是 8.1.2 - 构建缓存失效:Docker 构建时
COPY composer.lock .层没变,但内容已失效,导致复用旧缓存却装出新依赖
验证方式:ls -la composer.lock 和 git status composer.lock 应是 CI 第一步检查项。
跨环境平台约束不一致导致的隐性失败
composer.lock 不校验 PHP 扩展或小版本兼容性。若本地用 PHP 8.2.10 开发,而生产服务器是 PHP 8.2.5,且某依赖在 composer.json 中只写了 "php": "^8.2",lock 文件不会阻止它装一个需要 8.2.8+ 的扩展绑定版本。
- 正确做法:在
composer.json的config.platform下硬编码小版本,如"php": "8.2.5" - 生效前提:必须删除现有
vendor/和composer.lock,再跑composer install—— 旧 lock 文件不会自动重算 platform 约束 - CI 中建议加检查:
php -v | grep "8.2.5"+composer show php双验证
Docker 构建中 vendor 目录的缓存陷阱
常见错误是在 Dockerfile 中写:
COPY composer.json composer.lock ./ RUN composer install --no-dev --optimize-autoloader
这看似合理,但存在两个坑:
- 如果
composer.json和composer.lock时间戳新于vendor/,Docker 缓存仍会复用上一次构建的vendor/,哪怕 lock 文件内容已变 -
--optimize-autoloader生成的vendor/autoload.php是静态映射,若依赖树结构微调(如某包从 require 移到 require-dev),该文件不会自动重建
稳妥做法:在 RUN 前加 RUN rm -rf vendor/,或使用多阶段构建,在 builder 阶段彻底隔离缓存。
最常被跳过的动作是验证 config.platform 是否真正参与了 lock 文件生成——改完配置后不删 lock 就跑 install,等于什么都没做。











