结论:repositories + path 不是镜像,仅为开发临时软链接;自建本地 Composer 镜像仅两条正路——轻量静态索引(Satis)或全功能代理镜像(如阿里云 packagist-mirror、Artifactory),选错即踩坑。

直接说结论:你不需要、也不该用 repositories + path 搞“本地镜像”——它根本不是镜像,只是开发时临时软链接包的权宜之计。 真正要自建本地可共享、可复用、支持语义化版本和依赖解析的 Composer 镜像仓库,只有两条路:轻量静态索引(Satis)或全功能代理镜像(如阿里云开源方案)。选错方向,后面全是坑。
为什么 repositories + path 不是镜像
很多人在 composer.json 里写:
"repositories": [
{ "type": "path", "url": "./packages/*" }
]
这只会让当前项目从本地目录加载包,但:
- 不拉取 packagist.org 的任何包,不生成
packages.json元数据 - 不支持
^2.0或dev-main这类版本约束,Composer 会直接报Could not find package - 没有 HTTP 接口,CI/CD 机器、其他同事、Docker 构建都用不了
- 删掉
./packages目录,整个composer install就崩
Satis 是什么,适合谁用
Satis 是一个静态包索引生成器,不是实时镜像服务。它适合内网隔离、离线发布、或只同步少量私有包的场景。
- 构建后输出纯静态文件(
packages.json+dist/*.zip),扔到 Nginx/Apache 就能用 - 必须手动运行
php bin/satis build satis.json web/才更新,改了某个包的composer.json不会自动感知 - 默认只抓
dist归档,不保留source(git clone 地址),除非你在satis.json里加"archive": {"skip-dev": false} -
"require-all": true和"require"不能混用;"repositories"字段必须是对象数组,不能只填字符串 URL
想真正代理 packagist.org?别碰 Satis
如果你需要秒级同步官方源、支持所有包(包括带 GitHub ZIP 的)、允许团队随时 composer require 新包,Satis 做不到。得换方案:
- 阿里云开源的
packagist-mirror:基于 Go 实现,用 OSS 存 ZIP、Redis 管队列,需自己搭基础设施(OSS + Redis + Golang 环境) - Coding / JFrog Artifactory / Nexus Repository:提供 UI 和代理模式,配置好 upstream 就能自动缓存,但部分需付费或限免费版功能
- 私有 Packagist(commercial)或 Toran Proxy(已停更):企业级选项,权限、审计、Webhook 全都有,但成本高
注意:composer config repo.packagist composer https://xxx 只是换源,不是建镜像——它只是告诉 Composer “去哪查元数据”,背后服务还得你来部署。
最容易被忽略的三个实操细节
哪怕你选对了工具,这几个点一漏,composer install 就卡住或报错:
- Nginx/Apache 必须正确配置
dist/目录的 MIME 类型,否则 ZIP 下载会变成文本乱码,报file could not be downloaded - Satis 构建时若用
--no-interaction,但satis.json里"repositories"有私仓且需认证,会静默失败——建议先去掉该参数跑一次看日志 - 阿里云镜像方案中
GITHUB_TOKEN是硬性要求,没它,90% 的 dist ZIP 同步会失败,且错误提示极不明确(只报 “HTTP 403”)











