私有包必须显式配置repositories,禁用packagist.org,重建composer.lock,并确保Git仓库tag可访问。否则会因回退公共源或锁文件失效导致安装失败。

composer.json 中的 repositories 配置必须显式声明私有源
Composer 不会自动 fallback 到私有仓库,哪怕你已用 composer config repositories.xxx 添加过。项目级 composer.json 的 repositories 字段必须包含该包所在私有源,否则 composer install 仍会尝试从 packagist.org 拉取——此时若包已移除,直接报错 Could not find package xxx at version yyy。
实操建议:
- 将私有 Git 仓库(如 GitLab、GitHub Private、自建 Gitea)以
vcs类型写入repositories,URL 必须带认证信息或依赖 SSH key - 若使用私有 Packagist 服务(如 Satis、Private Packagist),则用
composer类型,并确保packagist.org被显式禁用(见下一条) - 避免混用多个同名包源;Composer 默认按数组顺序查找,但冲突时行为不可靠
必须禁用 packagist.org 防止意外回退
即使你在 repositories 里加了私有源,Composer 默认仍会把 packagist.org 当作兜底源。一旦私有源中缺少某个版本(比如只同步了 dev-main,没同步 v1.2.3),它就会去公共源找——而这时包已下架,报错 Could not find package xxx at version v1.2.3。
正确做法是显式关闭默认源:
{
"repositories": [
{
"type": "composer",
"url": "https://your-private-packagist.com"
},
{
"packagist.org": false
}
]
}
注意:{"packagist.org": false} 是一个特殊语法,必须作为独立对象放在 repositories 数组末尾,不能合并进其他源配置。
composer.lock 文件必须重生成,不能复用旧锁
旧 composer.lock 里记录的是包在 packagist.org 上的 dist URL、commit hash 和镜像校验值。迁移到私有源后,这些字段全部失效:dist URL 不可达、hash 可能因打包方式不同而变、甚至 commit hash 在私有仓库里可能被 force-push 覆盖。
安全做法是彻底重建锁文件:
- 删掉现有
composer.lock - 运行
composer update --lock(推荐)或composer install(仅当composer.json已完整适配私有源) - 检查新锁文件中
packages下该包的source.type是否为git或package,source.url是否指向私有地址
跳过这步会导致 CI 构建失败、本地环境与生产不一致,且错误信息模糊(常表现为 “corrupted zip” 或 “invalid signature”)。
私有 Git 仓库需确保 tag/branch 可被 Composer 解析
Composer 依赖 Git 的 ls-remote 和 archive 接口解析版本。如果私有仓库未启用 Git HTTP 服务(如 Nginx 未配置 git-http-backend),或 SSH 端口被防火墙拦截,composer update 会卡在 Loading composer repositories with package information 或报错 Failed to execute git clone。
验证方法:
- 手动执行
git ls-remote --tags --refs https://your-git/repo.git(HTTP)或git ls-remote --tags --refs git@your-git:repo.git(SSH) - 确认返回结果含有效 tag(如
refs/tags/v1.2.3),而非空或权限拒绝 - 若用 HTTPS,确保
auth.json正确配置 token;若用 SSH,确保~/.ssh/config匹配仓库域名且 key 已加载
GitLab/GitHub 私有库常见坑:token 缺少 read_package_registry 权限(GitLab)或 packages:read(GitHub),导致 API 返回 403 却被 Composer 静默忽略为“无此包”。










