离线静态源必须用 Satis 生成,因其依赖完整的 packages.json 和 dist/ZIP 文件供 composer install 直接读取;仅放 vendor/ 或 ZIP 而无索引文件会导致元数据缺失、校验失败或网络回退,须通过三重断网配置(删默认源、禁用全局源、设环境变量)并严格对齐 archive.prefix-url 与 Web 路径方可生效。

离线静态源必须用 satis 生成,不能靠改 repositories 或配本地 Web 服务假装可用——它依赖完整、可被 composer install 直接读取的 packages.json 和 dist/ ZIP 文件,否则会报 Could not fetch https://repo.packagist.org/packages.json。
为什么 nginx + 直接放 vendor/ 不行
Composer 安装时先请求 packages.json 获取包元数据(版本、依赖、dist URL),再按需下载 ZIP。只放 vendor/ 目录或 ZIP 文件本身,没有索引文件,composer install 根本不知道该装什么、装哪个版本。
- 把
vendor/整个目录扔进 Web 根目录,访问http://localhost/vendor/monolog/monolog-2.13.0.zip是能下载,但composer install不会猜路径,也不会扫描目录 - 用
php -S起一个本地服务器,不生成packages.json,照样报错file could not be downloaded: failed to open stream - 哪怕你手动写了个
packages.json,只要没按 Satis 规则生成校验字段(如dist.shasum)、没配好archive.prefix-url,composer install会在校验阶段失败并退出
satis build 必须在联网机完成
Satis 不是“部署即用”的服务,它是构建工具:必须在有网机器上运行 php bin/satis build 抓取包、生成 JSON、打包 ZIP,输出才是离线可用的静态文件集。
- 安装:
composer create-project composer/satis --stability=stable,不要用global require,避免插件冲突 -
satis.json中禁用 HTTPS 强制检查:"config": {"secure-http": false},否则内网 HTTP 服务会被拒绝 -
archive.directory设为"dist",archive.prefix-url设为你的内网地址,例如"http://192.168.10.50"(注意末尾不加/dist) - 运行
php bin/satis build satis.json output/后,output/packages.json和output/dist/*.zip才是一套完整静态源
离线机配置 repo 指向静态目录,且彻底断网行为
只加一条 repositories 配置远远不够,composer install 仍会 fallback 到 packagist.org,必须从三处同时切断:
- 项目级删掉
composer.json中所有含"url": "https://packagist.org"的repositories条目 - 全局禁用默认源:
composer config --global repo.packagist false - 设置硬性环境变量:
ENV COMPOSER_DISABLE_NETWORK=1(Docker 或 shell profile 中都得设) - 配置本地源:
composer config repositories.my-mirror composer http://192.168.10.50,URL 必须和satis.json中archive.prefix-url一致
验证是否真离线:别信 composer --version
composer --version 成功不代表离线安装可行。真正要测的是依赖解析和下载链路是否绕开了网络:
- 断网测试:
ip link set eth0 down(Linux)或拔网线 - 清空
vendor/和composer.lock(若已有 lock,先rm -rf vendor && rm composer.lock) - 执行:
composer install --no-plugins --no-scripts --no-autoloader --no-interaction - 若出现
Could not fetch http://192.168.10.50/packages.json,说明 Web 服务没起来或路径不对;若出现Could not fetch https://repo.packagist.org/...,说明某处repo.packagist配置没生效
最容易被忽略的是 archive.prefix-url 和实际 Web 服务路径的对齐——Satis 生成的 packages.json 里每个包的 dist.url 都由它拼接得出,错一个字符,ZIP 就 404。











