离线安装 Composer 依赖必须完整传输 vendor 目录、禁用网络和插件脚本,并强制重生成 autoload;仅拷贝 composer.lock 会因远程元数据校验失败或环境不一致导致“Package not found”或运行时类加载错误。

想把 Composer 依赖离线部署好,光拷贝一个 composer.lock 文件然后跑 composer install 是行不通的。结果往往是报错“Package not found”,或者更隐蔽地静默跳过某些包。问题的根源通常就出在这几件事没做全:没有提前把所有子依赖拉取到位、没有彻底禁用插件和脚本的网络调用,或者 PHP 环境不一致导致自动加载失败。
为什么 vendor 目录存在却 still 报错 “Package not found”
一个典型的场景:目标机器上明明有 vendor/monolog/monolog 这个目录,但执行 composer install 时,它却提示 monolog/monolog is not a vailable。这可不是文件真的丢了,而是 Composer 在安装阶段依然会尝试去校验远程的元数据——哪怕它最终并不需要下载任何东西。
- 根本原因在于,Composer 默认会去读取
packagist.org。所以,即便vendor/目录已经存在,只要没有显式地禁用网络,它就会发起 HTTP 请求。 - 这时候,环境变量
COMPOSER_DISABLE_NETWORK=1就成了必需品。没有它,任何--no-xxx参数都拦不住初始的校验流程。 - 还有一个细节:如果
composer.lock里某个包的dist.url字段是https://开头(而不是file://),并且没有配置本地仓库,那么在COMPOSER_DISABLE_NETWORK=1的环境下,Composer 会直接退出。 - 想验证网络是否真的被禁用了?有个土办法:临时在
/etc/hosts文件里加上一行127.0.0.1 packagist.org,然后再运行composer install -v。如果看到Connection refused的提示,那才算真正到位了。
离线部署唯一可靠路径:完整 vendor + lock + 禁插件脚本
别轻信“只传 composer.lock 和 composer.json 就行”的说法,也别指望 composer archive 能打包所有依赖——它可不会处理子依赖。真正能落地、不出错的方案,只有完整地打包整个 vendor/ 目录。
- 在能联网的机器上,先执行:
composer install --no-dev --prefer-dist --optimize-autoloader。这能确保所有子依赖都进入vendor/,并且剔除了仅用于开发环境的文件。 - 打包之前,记得手动删除两个东西:
vendor/bin/(Windows 下的软链接会失效)和vendor/composer/installed.json(这个文件里可能包含绝对路径,容易引发自动加载异常)。 - 在目标机器解压后,必须运行这条命令:
COMPOSER_DISABLE_NETWORK=1 composer install --no-plugins --no-scripts --no-autoloader。 - 最后,再补上关键一步:
composer dump-autoload -o。强制刷新自动加载器,避免因为路径变更或旧的缓存导致类找不到。
私有包和 platform 不兼容是静默失败重灾区
离线环境下,最危险的其实不是那些明晃晃的报错,而是“看起来安装成功了,但一运行就报 Class not found”。这类问题,往往源于 composer.lock 里记录了一些不兼容的约束,而 Composer 默认会选择跳过,并且不给任何提醒。
- 检查一下
composer.json,看看是否有"minimum-stability": "dev"或"prefer-stable": false这样的配置——它们会让锁文件记录下像dev-master这类不稳定的版本号,离线时根本无法解析。 - 确认
composer.lock里每个包的version字段都是语义化版本(比如2.10.0),而不是带dev-前缀的。可以用composer update --lock命令来强制重写锁文件,使其规范化。 - 如果锁文件里写着
"require": {"php": ">=8.2"},但目标机器是 PHP 8.0,那么 Composer 会直接跳过这个包的安装(而且不报错!),直到代码里真正require它时才会崩溃。 - 对于私有的 Git 包(比如来自 GitLab 的),必须提前将它们转换为
path类型的仓库,或者把对应代码克隆到本地,并在配置中声明:{"type": "path", "url": "/path/to/local/package"}。
缓存目录迁移不如 vendor 直传,但适合 CI 场景
如果你的部署流程是“CI 构建 → 推镜像 → 运行容器”这种模式,那么预先迁移 COMPOSER_CACHE_DIR 缓存目录,可能比打包整个 vendor/ 更轻量。不过,这种方法对环境的一致性要求也更高。
- 先在能联网的机器上,查看缓存路径:
composer config --global cache-dir,通常是~/.composer/cache。 - 执行一次完整的
composer install --prefer-dist,确保所有依赖包都以 zip 格式下载到了cache/files/目录里。 - 把整个
cache/目录复制到目标机器,并设置好环境变量:COMPOSER_CACHE_DIR=/path/to/cachedir。 - 在离线机器上运行:
COMPOSER_DISABLE_NETWORK=1 composer install --no-interaction --prefer-dist --no-plugins --no-scripts。 - 需要特别注意:
cache目录的内部格式会随着 Composer 版本升级而变化。务必保证离线机器上的composer --version与在线构建机完全一致。
说到底,真正的麻烦从来不是“怎么拷贝文件”,而是“拷贝完了能不能跑起来”。PHP 版本、扩展(比如 ext-zip、ext-openssl)、SAPI 类型(CLI 还是 FPM)、甚至是系统的 umask 权限,都可能导致 vendor/autoload.php 加载失败。所以,在上线之前,务必在目标环境里,用真实的 PHP SAPI 执行一次 require 'vendor/autoload.php'; 来验证。别只相信 composer install 命令输出的那行“Success”。
