必须由容器内composer生成vendor,不能宿主机同步;否则因php版本、扩展或opcache差异导致class not found等错误。正确做法是多阶段构建+.dockerignore排除vendor,并在开发时用空目录挂载/app/vendor覆盖但保留可读性。

直接排除 vendor 目录不是“加快同步”的解法,而是防止同步破坏依赖的必要操作。容器里 vendor 必须由 Composer 在容器内生成,不能靠宿主机同步进来——否则 PHP 版本、扩展、opcache 配置稍有差异,就会报 Class not found 或 opcache validation failed。
为什么不能把宿主机 vendor 挂进容器?
常见错误是这样写 docker-compose.yml:
volumes: - .:/app - ./vendor:/app/vendor
这会导致三类问题:
- 宿主机和容器 PHP 版本不一致时,
opcache或apcu缓存无法复用,类加载失败 - macOS/Windows 上 Docker Desktop 的文件系统层转发对
vendor/这种数万小文件目录性能极差,composer install可能卡在 “Loading composer repositories” -
composer.lock和实际安装的包版本错位,CI 构建通过,运行时报undefined method
正确做法:用 .dockerignore + 多阶段构建
核心逻辑是:构建时生成 vendor,运行时不传它,也不挂它。
- 在项目根目录加
.dockerignore,必须包含:/vendor/composer.phar/node_modules - Dockerfile 用多阶段构建,第一阶段装依赖,第二阶段只复制
vendor/和代码:COPY --from=builder /app/vendor /app/vendor -
COPY指令顺序必须严格:
先COPY composer.json composer.lock ./
再RUN composer install --no-dev --optimize-autoloader --classmap-authoritative
中间不能插任何其他RUN(比如chmod),否则缓存失效
开发时想热重载代码,又不想碰 vendor?
本地开发用 bind mount,但要明确排除 vendor 和 composer.lock:
volumes: - .:/app:cached - /app/vendor - /app/composer.lock
注意两点:
-
/app/vendor是空目录挂载,覆盖掉构建时生成的vendor,但 PHP 进程仍能读取(因为挂载点存在);真正生效的是容器内已安装的依赖路径 - 如果用了
composer dump-autoload --classmap-authoritative,就不能删vendor/再重装——classmap 是硬编码路径,删了就找不到类
最易被忽略的点:很多人以为 .dockerignore 能让 vendor 不进镜像就万事大吉,其实关键不在“忽略”,而在“谁来生成”。只要 vendor 不是由容器内 Composer 安装的,所有优化都白搭。











