GitLab CI 中 composer install 慢、失败、vendor 总重装,根本原因是 runner 环境干净、缓存未正确配置、PHP 环境不一致;必须提交 composer.lock、合理配置两级缓存(vendor/ 和 $HOME/.composer/cache)、确保 PHP 版本与扩展一致、正确配置私有包认证及 repositories。

GitLab CI 里 composer install 慢、失败、vendor 总重装,不是 Composer 本身有问题,而是 runner 环境干净 + 缓存没配对 + PHP 环境不一致这三件事没处理好。
为什么 vendor 每次都从头下载?
根本原因是:默认 runner 每次启动都是全新容器,vendor/ 目录不保留,composer.lock 又没提交或内容不匹配,导致 composer install 实际退化成 composer update。
-
composer.lock必须提交到仓库——它才是依赖版本的唯一真相,没它,CI 就失去可重现性 - 别在
.gitlab-ci.yml里写composer require或create-project,这些会改锁文件,破坏构建一致性 - 如果本地开发用 PHP 8.2,但 CI 镜像用的是
php:8.2-apache(含 Apache 模块)而没装mbstring或zip,composer install会静默跳过某些包,后续运行时才报Class 'Foo' not found
怎么配 cache 才真正复用 vendor?
GitLab 的 cache 不直接缓存 vendor/ 路径(斜杠会导致 key 冲突),而是靠两级缓存协同:Composer 下载缓存 + vendor 安装结果。漏掉任意一层,速度就回不去。
- 必须同时声明两个
paths:vendor/和$HOME/.composer/cache/ -
key推荐用"$CI_PROJECT_ID-composer-$CI_COMMIT_REF_SLUG-$CI_JOB_NAME",确保分支和作业维度隔离,避免dev缓存污染main -
restore-keys加一条 fallback:"$CI_PROJECT_ID-composer-main-",让 feature 分支能快速复用 main 的缓存冷启 - 加
--no-interaction --prefer-dist --optimize-autoloader参数,减少交互等待和解压开销
私有 GitLab 包拉不下来怎么办?
不是网络不通,是认证没走通,或者仓库根本没被 Composer “看见”。常见于用 HTTPS 方式拉取私有包却没配 auth.json,或部署密钥只绑了主项目、没启用在依赖仓库上。
-
repositories字段必须显式声明私有包地址,类型为vcs,且url要能用git clone手动成功 -
auth.json放项目根目录或~/.composer/auth.json,权限必须是600,内容用 Personal Access Token(glpat-xxx),不能用密码或 deploy token - 如果用 SSH,除了在 CI 中配置
SSH_PRIVATE_KEY环境变量,还必须去每个被依赖的私有 GitLab 仓库页面,在 “Privately accessible deploy keys” 列表中手动启用该密钥 - 检查
composer.json里引用的包名(如myorg/my-package)是否与 GitLab 项目路径完全一致——大小写、连字符、group 名都不能错
如何验证缓存和环境真生效?
GitLab UI 显示 “Cache loaded” 不代表 vendor/ 有内容,更不代表 autoloader 正确生成。很多问题卡在“看似缓存成功,实则空目录+旧 lock 文件+PHP 版本错位”的组合里。
- 在
composer install后加一行:ls -la vendor/autoload.php && composer dump-autoload -o,确认文件存在且能生成优化版自动加载器 - 在 job 开头跑
php -v和composer show php,对比输出是否与本地一致;再跑php -m | grep -E 'mbstring|xml|zip'确认扩展已加载 - 删掉
composer.json里的"platform"字段——它会让 Composer “假装”有某扩展,但 CI 环境实际没装,运行时报错才暴露
最常被忽略的点:缓存 key 看似合理,但 CI_COMMIT_REF_SLUG 在 tag 构建时是 tag 名,在 merge request 时是 merge-request-xxx,如果 key 没覆盖全场景,缓存就断层;另外,vendor/ 权限问题在某些 root 运行的私有 runner 上会导致缓存写入失败,但日志里不报错,只能靠 ls -la vendor 手动验证。


















