Satis 静态镜像生效需同时满足三条件:Satis 生成文件、Web 服务器正确托管、客户端显式禁用 packagist.org 并声明源;任一缺失将导致 composer install 静默回退外网或 404。

必须用 Satis 生成静态镜像 + Web 服务器托管 + 客户端显式声明源并禁用 packagist.org,三者缺一不可;否则 composer install 会静默 fallback 到外网或直接 404。
satis build 后为什么 composer install 还连 packagist.org
根本不是构建失败,而是客户端根本没读到你的镜像——Satis 不提供服务,只生成文件。问题一定出在链路中间:
- 运行
curl -I https://your-mirror.internal/packages.json,返回非 200 或 Content-Type 不是application/json,说明 Web 服务器没配对 -
composer config -g repo.packagist输出为空、null或纯字符串(如"https://packagist.org"),说明全局配置根本没写进去 - 项目级
composer.json里漏了"packagist.org": false,或它被写在repositories数组内部而非根节点 - 镜像 URL 少了末尾
/,导致 Composer 拼出https://your-mirror.internalpackages.json这种非法路径
Web 服务器配置最容易错的三个点
Nginx 是最常用选择,但以下配置项漏一个就会 404 或静默失败:
-
root必须精确指向satis.json中output-dir的绝对路径,例如root /var/www/satis;,不能多一层/web或少一层 - 必须显式声明 JSON MIME 类型:
types { application/json json; },否则返回text/plain,Composer 拒绝解析 -
dist/下 ZIP 文件需可直连 GET,常见拦截:SELinux 限制、Nginxdeny all、文件权限为600(应至少644)
客户端 composer.json 怎么写才真正走私有源
项目级配置优先级高于全局,且随代码交付,是唯一靠谱方式。但手写极易踩坑:
- 私有源必须作为
"packagist"键写入repositories对象(不是数组),形如:"repositories": { "packagist": { "type": "composer", "url": "https://your-mirror.internal/" } } -
"packagist.org": false必须和require同级,写在composer.json根节点,不能嵌套进任何仓库对象里 - 改完必须删掉
vendor/和composer.lock,再跑composer install;update会复用旧 lock 里的 dist URL,根本不会走新镜像 - 如果已有其他私有 Git 源(
"type": "vcs"),别用composer config repo.packagist命令——它会覆盖整个repositories字段;应手动编辑,确保结构是对象而非数组
satis.json 配置失效的隐蔽原因
很多构建成功但镜像无效,是因为结构或语义错误,Satis 不报错但静默跳过:
-
repositories必须是顶层数组,每个元素是完整对象:{"type": "vcs", "url": "https://git.internal/lib"},不能只写字符串 URL -
"require-all": true和"require": {...}不能共存,后者会覆盖前者;想镜像全部包就只留require-all -
output-dir强烈建议写绝对路径(如/var/www/satis),相对路径在 CI 或不同用户下容易解析失败 - 目标 Git 仓库根目录下必须有合法
composer.json,且name字段只能含小写字母和短横线(如my-org/my-package),含大写或下划线会导致 Satis 跳过该包
最常被忽略的是:Satis 构建后,你面对的不是一个“服务”,而是一堆静态文件。所有问题本质都是路径没对齐、MIME 没声明、或客户端配置没写对位置——没有中间态,要么全通,要么完全不走。


















