最可靠的离线部署方案是直接复制 vendor 目录,前提是源机与目标机的 PHP 版本、关键扩展、Composer 版本及环境完全一致,并通过 composer install --no-dev --optimize-autoloader 构建和验证。

直接复制 vendor 目录是最简单、也最常被低估的离线方案
只要源机和目标机环境一致,vendor 就不是“中间产物”,而是可直接部署的运行时资产。它不依赖网络、不触发脚本、不重建 autoload(除非你手动删了),比任何“假装离线”的命令更可靠。
常见错误现象:Class not found、undefined symbol、Extension missing——这些都不是复制错了,而是环境不匹配。
- PHP 版本必须一致(比如都是 8.2.12,小版本差也可能导致 opcache 或扩展 ABI 不兼容)
- 关键扩展要齐全:比如
mbstring、openssl、curl、json,缺一个就可能让某个包的 autoloader 或配置解析失败 - Composer 版本建议对齐(
composer --version查看),尤其涉及installed.json格式或平台约束解析时 - 打包前在源机执行:
composer install --no-dev --optimize-autoloader,再用一段最小代码验证:require 'vendor/autoload.php'; echo class_exists('Monolog\Logger') ? 'OK' : 'FAIL'; - 解压到目标机后,别急着跑
composer install;先composer dump-autoload -o刷新一次自动加载器,能绕过路径硬编码或 symlink 污染问题
composer install --no-plugins --no-scripts --no-autoloader 是“有 vendor 但不敢信”时的兜底命令
这个组合不是为了“跳过安装”,而是为了强制 Composer 只做一件事:校验 vendor 目录里的每个包,是否和 composer.lock 中记录的版本、哈希、dist URL 完全一致。不联网、不执行、不生成新文件——只报错,不修复。
使用场景:你不确定 vendor 是不是从同一份 composer.lock 生成的;或者团队里有人手改过 vendor 里的某行代码,你得快速确认有没有“脏改动”。
- 必须确保
composer.lock文件存在且未被修改(git status看一眼最准) - 如果输出里出现
Installing monolog/monolog (3.5.0): Loading from cache,说明 Composer 其实没走vendor,而是在查缓存——这不是你想要的,得关掉缓存或清空它 - 加
--dry-run可预览行为(Composer 2.5+ 支持),避免误删或覆盖 - 不加
--no-autoloader时,它仍会尝试写vendor/autoload.php,若权限不足或路径被挂载为只读,会直接中断
想在离线机上 require 新包?别碰 composer update,改用 path 仓库
composer update 在离线环境下几乎必然失败,因为它默认开启 packagist 回源机制,哪怕你配了本地仓库,只要某个包的某个版本不在本地索引里,它就会去查 packagist.org 的 DNS 并超时报错。真正能落地的方案,是让 Composer 把你硬盘上的目录当成“合法包源”。
核心不是“怎么装”,而是“怎么骗过 Composer 认出那是包”——靠的是 path 类型仓库 + 本地 composer.json。
- 把目标包(比如
my-utils)整个目录拷到离线机的/opt/my-packages/my-utils,里面必须含有效的composer.json(至少有name和version) - 项目根目录下执行:
composer config repositories.my-utils path "/opt/my-packages/my-utils" - 再运行:
composer require my-utils/my-utils:dev-main --no-plugins --no-scripts(注意写死分支名,别用^1.0) -
path仓库只对require生效;composer update会完全忽略它,这是设计使然,不是 bug - 该方式不生成 dist 包,也不进缓存,适合内部工具类库,不适合带编译逻辑(如 ext-c extensions)的包
缓存迁移看似省事,实则暗坑最多
把 ~/.composer/cache 整个目录拷过去,再设 COMPOSER_CACHE_DIR,听起来很美。但实际中,90% 的失败都发生在这里——不是没拷全,而是 Composer 没“认出来”。
最容易被忽略的点:缓存生效的前提是 composer install --prefer-dist,且 composer.lock 里所有包的 dist.type 必须是 zip 或 tar,不能是 source(比如私有 Git 仓库没配 dist 字段)。
- 联网机执行前先确认:
composer show monolog/monolog | grep type,输出应为type : library,且dist字段存在 - 离线机必须显式设置环境变量:
export COMPOSER_CACHE_DIR=/path/to/cachedir(Windows 用set),仅composer config --global cache-dir不够 - 加
-v参数运行:composer install -v --no-interaction --prefer-dist,看日志里是否出现Loading from cache;如果还在刷Downloading,说明缓存路径没生效,或包类型不匹配 - 不同 Composer 大版本(如 2.2 vs 2.5)缓存格式不兼容,混用会导致静默跳过或哈希校验失败
真正可靠的离线,从来不是靠“断网后还能不能跑命令”,而是靠“提前确认哪几个字节必须一致、哪几个环境变量不能少、哪几行输出才算成功”。细节藏在 composer.lock 的哈希值里,藏在 PHP phpinfo() 的扩展列表里,也藏在你执行 -v 时那一屏滚动的日志里。










