根本原因是copy . /app出现在run composer install之前导致缓存链断裂;必须先单独copy composer.json和composer.lock ./,再run install,最后copy . .,并配合buildkit cache mount复用dist包。

为什么 vendor 层总在 Docker 构建中反复解压
Docker 构建时 vendor 层重建,根本不是 Composer 慢,而是 COPY 指令顺序错误导致缓存链断裂。只要 COPY . /app 出现在 RUN composer install 之前,哪怕只改了一个 .env 文件,整个 vendor/ 就会重下、重解压、重生成 autoload —— 因为上层哈希变了,后续所有 RUN 指令缓存全部失效。
-
COPY . /app把所有易变文件(PHP 源码、日志、配置)一次性塞进镜像,该层永远无法复用 -
composer.lock换行符不统一(CRLF vs LF)也会让哈希不一致,导致缓存跳过 - 没显式
COPY composer.json composer.lock ./,Docker 就无法判断依赖声明是否真没变
如何让 vendor 层真正只构建一次
核心原则:让 composer install 这一层只依赖极少数稳定文件,并尽可能早执行。顺序错了,再多优化都白搭。
- 先
COPY composer.json composer.lock ./(注意路径结尾是./,不是/app) - 紧接着
RUN composer install --no-dev --prefer-dist --optimize-autoloader --classmap-authoritative --no-scripts - 最后
COPY . .(此时源码变更完全不影响前面的 vendor 层) - 必须确保
composer.lock已提交进 Git,且换行符统一:git config core.autocrlf input
BuildKit 下怎么跳过重复下载和解压
光靠 Docker 层缓存只能避免重装,但不能跳过下载和解压。要真正复用 dist 包,得靠 BuildKit 的 cache mount。
在 Linux 上通过 Docker 运行 OpenClaw,并使用 Tailscale 实现远程访问。⚠️ 涉及 sudo、Docker、Tailscale和凭证挂载——请先查阅安全章节...
- 启用 BuildKit:
export DOCKER_BUILDKIT=1或docker build --progress=plain ... - 在
RUN中挂载缓存: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是关键,不同构建间靠它共享已下载的 ZIP 包;target必须与COMPOSER_CACHE_DIR一致 - 验证路径:
composer config cache-dir默认是/root/.composer/cache,但容器内建议映射到/tmp/composer-cache更稳
哪些参数会让解压更轻量、更可控
--prefer-dist 不是可选项,它是控制解压行为的核心开关。不用它,Composer 可能回退到 git clone,I/O 压力翻倍。
-
--prefer-dist强制走预编译 ZIP 包,大幅减少小文件写入次数 -
--classmap-authoritative让 autoloader 完全信任 classmap,跳过文件系统扫描 —— 这直接降低后续 PHP 执行时的 I/O 频次 -
--no-scripts阻止post-install-cmd触发额外解压或生成逻辑(比如某些前端包会再解一个 node_modules) - 别信
--parallel-downloads能解决解压慢:它只加速下载,解压仍串行;并发设太高反而触发file_put_contents(/tmp/): failed to open stream(tmpfs inode 不足)
真正卡住的从来不是网络,而是你把易变文件和依赖安装混在同一层。一旦 composer.lock 没变,vendor/ 就不该动 —— 这个前提,必须靠指令顺序和 BuildKit cache mount 一起守住。










