根本原因是Composer默认启用packagist.org兜底源,当私有仓库未显式禁用默认源且返回“未找到包”时,会自动fallback至镜像查找;若内网包名与公开包重名,便拉取镜像中旧版而非私有仓库最新版。

Composer为什么在内网拉私有Git仓库时突然走镜像?
根本原因是 Composer 默认启用 packagist.org 作为兜底源,哪怕你配置了 SSH 地址,只要没显式禁用默认源,它就会在私有源返回“未找到包”后自动 fallback 到镜像或 Packagist 查找——而内网 Git 仓库的包名若与公开包重名(比如 myorg/utils 碰巧和某个废弃开源包同名),Composer 就会从镜像拉取旧版,完全绕过你的私有仓库。
这不是协议冲突,是源匹配顺序被 hijack。常见现象:执行 composer install 后 vendor/myorg/utils 目录下版本是 v1.2,但你私有 Git 仓库里最新 tag 是 v2.5,且 composer show -a myorg/utils 显示 source 是 https://mirrors.aliyun.com/composer/ 而非你的 git@git-server.internal:group/repo.git。
- 必须在
composer.json根级加"packagist": false,不能放在repositories里 - 确认私有仓库
type正确:"type": "vcs"对应 Git URL,"type": "composer"对应 Satis/Toran 的 packages.json 接口 - 如果用了
"type": "vcs",确保远程 Git 仓库根目录的composer.json中"name"字段与require完全一致(大小写敏感)
SSH URL 写对了,为什么还走 HTTPS 镜像?
Git 自身的 insteadOf 规则会劫持 URL 协议。即使你在 composer.json 里写的是 "git@git-server.internal:group/repo.git",只要本地 Git 配置了全局规则把 SSH 替换成 HTTPS,Composer 就会静默使用 HTTPS 地址发起请求,然后被镜像拦截。
运行 git config --global --get-regexp insteadof 查看是否有类似 url."https://git-server.internal/".insteadOf 的配置。如果有,且指向 HTTPS,就说明问题在这里。
- 清除错误规则:
git config --global --unset url."https://git-server.internal/".insteadOf - 检查
~/.gitconfig和/etc/gitconfig是否存在硬编码的insteadOf - CI 环境中尤其要注意:Docker 镜像或基础镜像可能预置了这类规则,需在构建阶段显式清理
镜像加速和私有 Git 拉取能共存吗?
能,但必须分层控制:镜像只作用于 "type": "composer" 类型的源,对 "type": "vcs"(即直接 Git 克隆)完全无效。所以如果你的私有仓库是 Git 地址,镜像不会加速它,也不会干扰它——除非你误配了源类型或触发了 fallback。
真正危险的是混用:比如把 Satis 私有源("type": "composer")和 Git 私有源("type": "vcs")写在同一 repositories 数组里,且 Git 源排在前面。这时 Composer 会先查 Git 源,但因 type: vcs 不提供完整元数据(只认根目录 composer.json),很可能返回“未找到”,于是立刻 fallback 到下一个源——如果下一个是镜像源,就彻底失控。
- 策略上,把
"type": "composer"的私有源(如 Satis)放在repositories数组最前,确保它优先响应所有包查询 - Git 私有源(
"type": "vcs")只用于明确指定的单个包,不要让它参与通用包发现 - 镜像配置(
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/)仅影响默认源行为,对已声明的私有"type": "composer"源无影响
CI 环境下 SSH + 私有 Git + 镜像三者同时生效的关键点
CI 中最容易出问题的不是配置本身,而是环境隔离导致的 SSH agent 和 known_hosts 缺失。本地能通的 SSH 配置,在 CI 里往往因为缺少交互式终端、未启动 agent、host key 未验证而静默失败,最终 fallback 到镜像,拉错版本。
典型错误现象:composer install 卡在 Cloning into 'xxx',或报 Permission denied (publickey),但你确认密钥内容正确——其实是 ssh-agent 没启动,或者 known_hosts 为空导致连接阻塞。
- GitHub Actions 必须显式启动 agent:
eval $(ssh-agent -s),再ssh-add <(echo "${{ secrets.INTERNAL_SSH_KEY }}") - GitLab CI 或 Jenkins 需提前执行:
ssh-keyscan git-server.internal >> ~/.ssh/known_hosts - 私钥内容若含 Windows 换行符(
\r\n),ssh-add会静默失败;注入前用tr -d '\r'清理 - 内网 Git 服务器若用非标 SSH 端口(如 2222),
~/.ssh/config中必须明确写Port 2222,且 Git URL 改为git@git-server.internal:2222/group/repo.git
最常被忽略的是:镜像配置和 SSH 配置不在同一抽象层。镜像管元数据下载,SSH 管 Git 克隆。两者不打架,但一旦 SSH 失败,Composer 就会放弃克隆,转而尝试从其他源解析——这时候,"packagist": false 是否生效,就成了唯一防线。


















