Satis 报错“Could not fetch packages.json”是因为其生成静态文件而非运行服务,需确保 URL 可直接 curl 获取 JSON,常见原因包括 Web 服务器未配置默认入口、URL 缺少结尾斜杠、文件权限导致 403。

为什么直接配了 URL 还是报 Could not fetch packages.json
因为 Satis 生成的是静态文件,不是运行服务——你配的 url 必须能直接 curl https://your-satis-server.com/packages.json 返回 JSON,否则 Composer 就会失败。常见原因有三个:
• Web 服务器没把 packages.json 设为默认入口(Nginx/Apache 要配 index 或显式路由)
• URL 少了结尾斜杠:https://satis.example.com 和 https://satis.example.com/ 在 Composer 解析时行为不同,后者才对
• satis build 输出目录权限不对,Web 用户读不到 packages.json 或 ZIP 文件,导致 403
项目 composer.json 里怎么写 repositories 才生效
必须满足三个硬性位置与逻辑条件,缺一不可:
• repositories 是顶层字段,不能嵌在 require、config 或其他字段里
• 私有仓库对象必须放在 repositories 数组首位,Composer 按顺序查找,命中即停
• 显式关闭公共源:"packagist.org": false 要作为独立对象写进 repositories 数组,不是 config 子项
• 示例正确写法:
{
"repositories": [
{
"type": "composer",
"url": "https://satis.internal.example.com/"
},
{ "packagist.org": false }
],
"require": { ... }
}
离线环境安装失败,90% 是因为漏了这三件事
离线部署不是“配对 URL 就完事”,而是依赖链必须提前穷举并镜像完整:
• satis.json 的 require 字段要列全所有直接 + 间接依赖,包括 psr/log 这类底层包;用 composer show --tree 在联网机器上展开后复制
• archive.skip-dev 必须设为 false,否则 require-dev 工具链(如 phpunit、phpstan)不会被打包
• 项目级 composer.json 里不能残留 packagist.org 配置,得靠 composer config repository.my-satis composer https://... 替换,再用 composer config -l | grep repo 确认只剩私有源
认证失败时别往 URL 里硬塞密码
HTTP Basic Auth 或 Bearer Token 写在 URL 里(如 https://user:pass@satis.example.com/)看似快,但会泄漏到日志、Git 历史、CI 缓存中。更安全的做法是:
• 在项目根目录放 auth.json,内容为:{"http-basic": {"satis.example.com": {"username": "xxx", "password": "yyy"}}
• CI 环境统一用 COMPOSER_AUTH 环境变量注入 JSON 字符串
• 注意:如果 Satis 用的是自签名 HTTPS 证书,还得在 config 里加 "secure-http": false,否则 Composer 直接拒绝连接
Satis 的核心约束很朴素:它不代理、不转发、不实时响应。所有“不稳定”都来自把静态产物当动态服务用,或把依赖管理责任错当成配置动作。真正卡住人的,永远是那个没列进 satis.json 的间接依赖,或者那行被忽略的 "packagist.org": false。











