根本原因是copy . .出现在composer install之前,破坏了docker层依赖链;必须最早单独copy composer.json和composer.lock,且二者须已提交至git,再run install,最后copy . .。

为什么 vendor 层总不命中缓存?
根本不是 Composer 本身的问题,而是 Docker 构建层依赖链被破坏。只要 COPY . . 出现在 composer install 前面,哪怕只改了一个 README.md,Docker 就会丢弃所有后续层缓存,包括 vendor/ 目录。
常见错误现象:
-
Step 5/10 : RUN composer install每次都重新下载、解压、安装 - CI 构建耗时从 30 秒涨到 3 分钟
- 本地构建快、CI 构建慢,且报错如
Package foo has a PHP requirement incompatible with your PHP version
真正起决定作用的只有两点:
-
composer.json和composer.lock必须最早被COPY,且不能混在其他文件里 - 这两个文件必须已提交到 Git,不能本地生成、未提交或被
.dockerignore过滤掉
如何写一个能复用 vendor 层的 Dockerfile?
顺序错了,再好的参数也没用。必须严格按这个节奏来:
-
COPY composer.json composer.lock ./—— 这是缓存判断的唯一输入 -
RUN composer install --no-dev --no-scripts --optimize-autoloader --no-interaction --prefer-dist—— 所有参数都要服务于“确定性” -
COPY . .—— 放最后,且确保.dockerignore里有/vendor,防止覆盖
关键细节:
- 不要在
COPY composer.json composer.lock ./前加任何RUN(比如mkdir或chown),除非它绝对稳定、不引入变量 -
--no-scripts和--no-plugins能排除钩子执行带来的不确定性,尤其避免本地开发时注入的 post-install-cmd - 如果项目用了
COMPOSER_CACHE_DIR,建议设为/dev/null,让缓存完全交给 Docker 层管理,不引入额外状态
BuildKit 的 --mount=type=cache 值不值得开?
它能加速包下载,但解决不了 vendor/ 层复用这个核心问题。而且容易在 CI 中翻车。
典型风险场景:
- 多个 job 并发构建时,
id=composer-cache共享冲突,导致依赖解析失败 - 不同 PHP 版本 job 复用同一 cache mount,出现平台要求不兼容报错
- 本地开发开了 cache mount,CI 没开,行为不一致,调试困难
更可控的替代方案:
- 在 GitHub Actions 中用
actions/cache缓存~/.composer/cache目录 - CI runner 宿主机挂载
/home/ci-user/.composer/cache到容器内对应路径 - 绝大多数团队发现:靠分层
COPY+ 锁文件稳定,已覆盖 90%+ 场景
多阶段构建中怎么避免 vendor 被污染或覆盖?
最终镜像里出现 vendor/bin/phpunit 或 vendor/symfony/console/Tests/,说明清理没做干净。
builder 阶段要主动瘦身:
- 安装后立刻运行:
RUN find vendor/ -name '.git' -prune -exec rm -rf {} + 2>/dev/null || true - 删测试和文档:
RUN find vendor/ -name 'tests' -o -name 'Tests' -o -name 'docs' | xargs rm -rf 2>/dev/null || true - 最终阶段只复制必要内容:
COPY --from=builder /app/vendor /var/www/html/vendor,别用COPY --from=builder /app /var/www/html
最容易被忽略的一点:如果你在 builder 阶段执行了 composer update,或者没提交更新后的 composer.lock,那整个缓存链就失效了——不是技术问题,是协作流程断了。











