离线环境下Composer必须手动导出并注入packages.json,因缓存目录不包含该根级元数据文件;需在联网机用composer archive或Satis生成后,按正确路径层级(~/.composer/cache/repo/repo.packagist.org/)和权限放置,并设COMPOSER_CACHE_DIR指向该父目录且禁用全局Packagist。

离线环境下 Composer 的元数据(packages.json)无法靠缓存目录自动复用,必须显式导出并注入,否则 composer install 会卡在 “Reading packages.json from cache” 或直接报 Could not fetch https://repo.packagist.org/packages.json —— 因为 ~/.composer/cache/repo/ 下缺的是元数据文件本身,不是 ZIP 包。
为什么 ~/.composer/cache/ 拷过去没用
Composer 缓存目录里,files/ 存 ZIP 包,repo/ 存元数据;但 repo/ 下的子目录(如 repo.packagist.org/)只缓存了具体包的 package.json(单包信息),不包含根级的 packages.json(全量索引)。这个文件是 Composer 启动时必读的“目录表”,只能从远程源实时拉取,且不进缓存。
- 即使你把整个
~/.composer/cache/复制过去,repo/里也没有packages.json,composer install就会失败 -
COMPOSER_DISABLE_NETWORK=1不会跳过对packages.json的校验逻辑,它只是阻止 HTTP 请求——而缺失该文件时,Composer 直接退出,不给 fallback 机会 - 验证方式:在目标机运行
ls -l ~/.composer/cache/repo/repo.packagist.org/,如果看不到packages.json,就确认缺失
手动导出 packages.json 的两种可靠方式
必须在联网、同版本 Composer 环境中执行,生成后拷入离线机对应路径。
- 方式一(推荐):用
composer archive+ 提取元数据
在联网机执行:composer archive --format=json --no-dev --output-dir=/tmp/mirror/ monolog/monolog:^3.0,然后从生成的/tmp/mirror/中提取packages.json(它会被写入输出目录根) - 方式二:用 Satis 构建静态镜像后直接取用
配置satis.json后运行php bin/satis build satis.json web/,生成的web/packages.json就是完整元数据文件,可直接复制到离线机的~/.composer/cache/repo/repo.packagist.org/下(需先创建该路径) - 注意:文件权限要设为可读,且 PHP 进程用户(如
www-data)必须有访问权;若用 Docker,挂载时加:ro并确保 UID 匹配
离线机上如何让 Composer 读到这个 packages.json
不能只放文件,还要骗过 Composer 的路径解析逻辑——它默认只认远程 URL 对应的缓存路径,不会主动扫描本地。
- 必须设置环境变量:
COMPOSER_CACHE_DIR=/path/to/your/cache,指向你存放packages.json的父目录(比如/opt/composer-cache) - 确保该路径下结构为:
/opt/composer-cache/repo/repo.packagist.org/packages.json - 禁用全局 Packagist:
composer config --global repo.packagist false,否则 Composer 仍会尝试 fallback 到远程 - 验证是否生效:在离线机运行
composer install -v,看到日志中出现Loading composer repositories with package information from cache,且不再报Could not fetch,即成功
真正落地时最常被忽略的是路径层级和权限:离线机上 packages.json 放错一层(比如放在 repo/ 下而非 repo/repo.packagist.org/),或文件属主是 root 而 PHP 进程以 www-data 运行,都会导致静默失败——它不报错,只是继续走网络 fallback,直到超时。











