真正可行的方案是BuildKit的--mount=type=cache配合稳定lock文件和显式缓存路径;因Docker每层均为干净容器,~/.composer/cache不跨构建复用,且COPY混入时间戳、.git或IDE文件会直接破坏层缓存。

缓存不持久,构建就慢;但硬塞宿主机缓存进镜像,又破坏可重现性——真正可行的方案只有 BuildKit 的 --mount=type=cache,配合稳定 lock 文件和显式缓存路径。
为什么 composer install 总是重下所有包?
Docker 默认每层都是干净容器,~/.composer/cache 不跨构建复用,哪怕 composer.lock 没变,也会重新下载 zip 包。更糟的是:如果 COPY 时混入了时间戳、.git 目录或 IDE 配置文件,Docker 层缓存直接失效,导致每次构建都走完整安装流程。
-
composer.lock必须在构建上下文里,且不能被 .dockerignore 排除(否则RUN composer install会退化成update) - 不要在
RUN composer install前COPY . .—— 这会让任何源码改动都污染依赖层 - Alpine 镜像默认没装
ca-certificates,SSL 报错会中断缓存复用,得先RUN apk add --no-cache ca-certificates
--mount=type=cache 怎么写才不白配?
BuildKit 的 cache mount 不是开关一开就生效,它依赖三个硬条件:启用 BuildKit、指定唯一 id、target 路径必须匹配 Composer 实际缓存位置。漏一个,缓存就形同虚设。
- 启用方式:
DOCKER_BUILDKIT=1 docker build .或export DOCKER_BUILDKIT=1 - 必须写全:
RUN --mount=type=cache,id=composer,target=/root/.composer/cache composer install --no-dev --optimize-autoloader - 查真实缓存路径:
composer config cache-dir,别假设是/root/.composer/cache—— 某些自定义镜像可能改过 - CI 环境中,不同 runner 若未共享 cache 存储后端(如 buildkitd 的
--oci-worker-gc配置),id=composer也起不了作用
多阶段构建里,缓存怎么跨阶段传递?
BuildKit cache mount 只作用于单个 RUN 指令,它不能把 vendor 目录“传”给下一阶段。vendor 的复用靠的是 Docker 层缓存本身,而 cache mount 只负责加速包下载。两者要分清职责。
- 第一阶段(builder):用
--mount=type=cache加速composer install,生成/app/vendor - 第二阶段(runtime):用
COPY --from=builder /app/vendor ./vendor拿 vendor,不依赖 cache mount - 如果 builder 阶段用了
--no-scripts,但 runtime 需要 post-autoload-dump,得在第二阶段单独跑一次composer dump-autoload --classmap-authoritative(注意权限) - 别在 builder 阶段
COPY . .后再composer install—— 这会让整个 vendor 层无法缓存
最易被忽略的点:BuildKit cache mount 的 id 是全局唯一的,如果多个项目共用同一个 CI runner 且都用 id=composer,缓存会互相污染,导致装出 PHP 版本不兼容的包。实际项目中,应按 PHP 版本打标,比如 id=composer-php82。


















