根本原因是copy . /app放在composer install之前破坏了docker层缓存链;必须先单独copy composer.json和composer.lock,再run install,最后copy . .,且lock文件须提交至git并统一换行符。

为什么 COPY . /app 总让 vendor 层失效
根本不是 Composer 慢,是 Docker 缓存被你无意中打断了。只要 composer.lock 没变,vendor/ 就不该重装——但现实中它总在重装,说明构建顺序踩了坑。
典型错误写法:COPY . /app 放在 RUN composer install 前面。Docker 构建时逐层计算哈希,一旦整个项目目录(含 .env、日志、PHP 文件)提前进镜像,哪怕只改一个空格,composer install 这一层哈希就全崩,所有包重新下载解压。
-
COPY . /app把高频变更文件一次性塞进去,缓存永远不命中 - 容器里默认用不上宿主机的
~/.composer/cache,因为没挂载进来 - 路径结尾漏掉斜杠(比如写成
COPY . /app而非COPY . /app/)会导致文件落点错位,vendor 被覆盖或丢失
怎么让 vendor 层真正复用
核心就一条:让 composer install 这层只依赖极少数稳定文件,并尽早执行。
MiniMax 图片理解 + 网络搜索 MCP 工具。适配 Docker 环境(极空间等),支持图片 OCR 识别、图像内容理解、网络搜索。API Key 安全存储在本地 credentials 文件,不暴露在代码中。
- 先
COPY composer.json composer.lock ./—— 这两文件变动频率低,哈希稳定 - 立刻
RUN composer install --no-dev --prefer-dist --optimize-autoloader --no-scripts -
--no-scripts防止某些包的post-install-cmd触发额外逻辑,破坏可重现性 - 最后
COPY . .(注意是.不是/app),确保源码变更不影响前面的 vendor 层 - 必须把
composer.lock提交进 Git,且统一换行符(git config core.autocrlf input),否则 CRLF/LF 差异会让哈希不一致
BuildKit 下怎么复用已下载的 dist 包
光靠 Docker 层缓存只能避免重复安装,但不能跳过下载。要用 --mount=type=cache 才能真正复用已下载的包。
- 启用 BuildKit:
export DOCKER_BUILDKIT=1或docker build --progress=plain RUN --mount=type=cache,id=composer-cache,target=/tmp/composer-cache COMPOSER_CACHE_DIR=/tmp/composer-cache composer install --no-dev --prefer-dist-
id=composer-cache是关键,不同构建间靠这个 ID 共享缓存;不写id等于每次新建一个空缓存 -
target必须和COMPOSER_CACHE_DIR一致,可通过composer config cache-dir查实际路径,默认是/root/.composer/cache,但容器内映射到/tmp/composer-cache更稳
多阶段构建里 vendor 怎么安全移交
构建阶段装依赖,运行时阶段只留代码和 vendor,这是减小镜像体积和提升安全性的标准做法。
- 构建阶段用
FROM composer:2 AS composer_build,只负责composer install - 运行时阶段用轻量镜像(如
php:8.2-fpm-alpine),COPY --from=composer_build /app/vendor /var/www/html/vendor - 务必加
--no-dev和--optimize-autoloader,否则运行时会带一堆开发工具和未优化的 autoload - 不要在运行时阶段再跑
composer update——它会改composer.lock,而该文件由 Git 管控,容器里改了也提交不了 - 国内环境记得设镜像源,否则
file could not be downloaded: failed to open stream: Connection timed out几乎必现
composer.lock 的换行符一致性与 BuildKit cache 的 id 命名。这两处出错,缓存就形同虚设。










