根本原因是copy . /app写在composer install之前导致docker层缓存失效;必须先单独copy composer.json和composer.lock ./,再run composer install --no-dev --prefer-dist --optimize-autoloader,最后copy . .,确保vendor层复用。

为什么 vendor 层总在重复安装?
根本不是 Composer 本身慢,而是 Docker 构建时 COPY . /app 放在 composer install 前面,导致每次改个 README.md 或 .env,整个依赖层哈希就变,vendor/ 必须重装。Docker 缓存是线性逐层校验的,只要上一层变了,后面所有 RUN 都失效。
常见错误写法:
COPY . /app RUN composer install --no-dev
这等于把所有不稳定文件(日志、配置、PHP 源码)提前塞进缓存链,composer.lock 再稳也没用。
-
COPY必须拆开:先只COPY composer.json composer.lock ./ - 中间不能插
RUN mkdir、COPY .env等任何额外指令 -
composer.lock必须提交进 Git,且换行符统一(git config core.autocrlf input) - 路径末尾别漏斜杠:
COPY composer.json composer.lock ./≠COPY composer.json composer.lock .(后者可能落错位置)
多阶段构建中 vendor 怎么精准复制?
最终镜像里只该有运行时必需的东西:PHP 运行环境 + vendor/ + 应用代码。builder 阶段装完依赖后,final 阶段不能 COPY --from=builder /app .,否则会把 composer.json、测试文件、.git 全带进来。
正确做法是显式限定路径:
COPY --from=builder /app/vendor /var/www/html/vendor COPY --from=builder /app/src /var/www/html/src
关键点:
- builder 和 final 阶段的 PHP 版本、已启用扩展(
php -m)、composer版本必须一致,否则 autoload 可能失败 - builder 阶段清理掉
vendor/里的.git、tests/、docs/:RUN find vendor/ -name '.git' -prune -exec rm -rf {} + 2>/dev/null || true - final 阶段基础镜像不要装
composer、git、zip等构建工具——它们只会增大体积、暴露攻击面
BuildKit cache mount 是不是必须开?
它能复用下载好的 dist 包,但解决不了 vendor 层复用这个核心问题。多数场景下,靠分层 COPY + 稳定 composer.lock 已足够;开了反而容易出问题。
典型风险:
- CI 多 job 并发时,
id=composer-cache可能冲突,报错如Package foo has a PHP requirement incompatible with your PHP version - cache mount 不跨节点共享,本地快、CI 慢,排查困难
-
target和COMPOSER_CACHE_DIR必须严格一致,查默认值用composer config cache-dir
更可控的替代方案:CI 中挂载宿主机 ~/.composer/cache 到容器内指定路径,不依赖 BuildKit。
哪些参数真正影响缓存稳定性?
不是参数越多越稳,关键是排除运行时不确定因素。比如 --no-scripts 能防止某些包的 post-install-cmd 触发副作用,破坏可重现性;--ignore-platform-reqs 虽然能让构建通过,但掩盖了 PHP 版本或扩展缺失的真实问题,最终镜像可能在目标环境直接 crash。
-
--no-dev:跳过 dev 依赖,避免不同机器装出不同版本的phpunit等 -
--no-scripts:禁用所有 composer 脚本钩子,确保构建行为确定 -
--optimize-autoloader:生成静态映射,减少运行时扫描,也降低后续RUN的副作用 -
--prefer-dist:优先用压缩包而非 git clone,更快更稳定 - 别用
--ignore-platform-reqs:它让缓存“看起来”有效,实际埋雷
最易被忽略的点:国内环境没配镜像源,composer install 卡在下载环节,误以为是缓存问题——其实连第一层都跑不完。











