
1. 项目缘起与整体设计思路1.1 为什么会走到“离线部署私有 npm 仓库”这一步前端团队规模一旦超过十个人node_modules就会变成一场灾难。同一个lodash版本A 同学装的是 4.17.20B 同学装的是 4.17.21CI 上跑出来的结果就是不一样。更别提某些内网开发环境物理隔离外网 npm registry 根本连不上每次装依赖都得靠 U 盘拷node_modules压缩包一个项目动辄几百兆传一次半小时。我所在的团队就踩过这个坑。早期大家各自为战谁需要什么包谁自己想办法。后来项目多了版本冲突、依赖缺失、构建失败的问题集中爆发。痛定思痛决定在内网搭一个私有的 npm 仓库把所有依赖统一管起来。选型上对比过几个方案Verdaccio 轻量但功能偏少cnpm 生态绑定较深Nexus3 虽然重一些但它同时能管 npm、Maven、Docker、PyPI一个服务解决多语言依赖问题长期看更划算。标题里提到的“Yarn 离线部署”是另一个关键点。Nexus3 本身需要 Java 运行环境安装包也不小如果目标服务器完全断网就得提前把所有依赖准备好。Yarn 在这里扮演两个角色一是作为包管理器用来在联网机器上把 Nexus3 所需的 npm 包如果走 npm 方式安装或前端项目依赖预先下载二是利用 Yarn 的离线镜像offline mirror功能把依赖缓存到本地目录再整体搬到内网。这套方案解决的核心问题是在内网隔离环境下搭建一个可持续维护的私有 npm 仓库让前端团队能够正常安装、发布、管理 npm 包同时保证版本一致性和构建可重复性。适合谁来参考如果你所在团队面临以下情况之一这篇内容应该能帮到你内网开发无法访问外网 npm团队需要统一管理私有包CI/CD 流水线需要稳定的依赖来源或者你只是想了解 Nexus3 在 npm 场景下的完整配置流程。1.2 整体架构与数据流向先把这个方案的骨架说清楚。整个体系分三个部分第一部分是 Nexus3 服务端部署在内网一台 Linux 服务器上负责存储所有 npm 包包括代理缓存的外网包和团队自己发布的私有包。它对外暴露一个 HTTP 端口前端开发者通过配置.npmrc或.yarnrc指向这个地址。第二部分是离线资源准备在一台能联网的机器上完成。需要下载 Nexus3 的安装包、Java 运行环境如果服务器没有、以及 Yarn 本身。如果 Nexus3 选择用 npm 方式安装不推荐后面会解释还需要把相关 npm 包也离线化。第三部分是客户端配置每个前端开发者的机器上通过 Yarn 或 npm 的配置把 registry 指向内网 Nexus3 地址。Yarn 的离线镜像功能可以进一步在本地缓存 tarball减少对 Nexus3 的网络请求。数据流向是这样的开发者执行yarn add→ Yarn 向 Nexus3 发起请求 → Nexus3 检查本地是否有该包 → 有则直接返回没有则根据配置的远程仓库地址去外网拉取如果 Nexus3 本身能通外网或返回失败完全隔离环境→ 包被缓存到 Nexus3 的 blob store 中 → 返回给客户端。在完全隔离的环境中Nexus3 无法主动去外网拉包所以需要提前把常用的包“预热”进去。预热的办法有两种一种是在联网的 Nexus3 上先拉一遍然后把整个数据目录搬过去另一种是用 Yarn 的离线镜像在联网机器上把依赖下载好再通过npm publish或 Nexus3 的上传接口批量导入。注意Nexus3 的 blob store 不能简单复制因为索引和元数据是分开存储的。迁移时建议用 Nexus3 自带的导出功能或者用npm publish重新发布。2. 核心细节解析与实操要点2.1 Nexus3 安装方式的选择与取舍Nexus3 官方提供多种安装方式Docker 镜像、tar.gz 压缩包、rpm/deb 包。在离线环境下Docker 方式反而最麻烦因为你还得离线安装 Docker 本身而且镜像导入导出也占空间。我推荐用tar.gz 压缩包解压即用依赖最少。具体来说需要准备这些东西资源说明获取方式Nexus3 tar.gz主程序约 200MB联网机器下载JDK 8 或 11Nexus3 依赖 Java联网机器下载对应平台版本Yarn 安装包客户端包管理器联网机器下载常用 npm 包 tarball预热用联网机器用 Yarn 下载Nexus3 从 3.24 版本开始要求 Java 11之前版本 Java 8 即可。如果你的服务器已经装了 Java跳过这一步。没有的话下载一个 OpenJDK 的 tar.gz 包解压后配置JAVA_HOME就行不需要 root 权限。这里有个坑Nexus3 默认监听 8081 端口如果服务器上这个端口被占用需要改nexus-default.properties文件里的application-port。另外Nexus3 默认以nexus用户运行如果你用 root 解压需要chown一下否则启动会报权限错误。2.2 Yarn 离线镜像的配置与使用Yarn 的离线镜像功能是这套方案的关键。它的原理很简单Yarn 在安装依赖时除了把包放到node_modules还会在指定的镜像目录里存一份 tarball 副本。下次安装同样的包时直接从镜像目录读取不再走网络。配置方法是在.yarnrc文件里加一行yarn-offline-mirror ./npm-packages-offline-cache yarn-offline-mirror-pruning true第一行指定镜像目录第二行表示每次安装后自动清理不再使用的包。这个目录会随着项目依赖变化而增长定期清理是必要的。在联网机器上进入项目目录执行yarn install所有依赖的 tarball 就会出现在npm-packages-offline-cache目录里。把这个目录和yarn.lock一起拷贝到内网机器再执行yarn install --offlineYarn 就完全不会发起网络请求。但这里有个细节yarn-offline-mirror只对yarn.lock里锁定的包生效。如果package.json里有新包没进 lock 文件离线安装会失败。所以流程必须是联网机器先yarn install生成完整 lock 和镜像再整体搬迁。实操心得镜像目录里的文件命名是包名-版本号.tgz如果手动往里面加包命名必须符合这个规则否则 Yarn 找不到。2.3 Nexus3 中 npm 仓库的三种类型Nexus3 里创建 npm 仓库时会让你选类型一共三种hosted宿主仓库用来存放团队自己发布的私有包。只有这种仓库允许npm publish。一般命名为npm-hosted或npm-private。proxy代理仓库用来代理外网的 npm registry。Nexus3 会缓存从外网拉取的包下次请求直接返回缓存。命名为npm-proxy远程地址填https://registry.npmjs.org。在完全隔离环境里这个仓库可以建但实际拉不到包主要靠 hosted 仓库。group组仓库把多个 hosted 和 proxy 仓库聚合成一个地址。客户端只需要配置 group 仓库的地址就能同时访问私有包和代理包。命名为npm-group成员里把npm-hosted和npm-proxy都加进去。客户端的.npmrc或.yarnrc里配置的 registry 地址应该指向 group 仓库而不是单独的 hosted 或 proxy。这样发布和安装走同一个入口管理起来简单。仓库类型用途是否可发布客户端是否直连hosted存私有包是否通过 groupproxy代理外网否否通过 groupgroup聚合入口否是2.4 权限与安全配置要点Nexus3 默认管理员账号是admin密码在admin.password文件里首次登录后会要求修改。在离线环境里这个密码文件在sonatype-work/nexus3/admin.password。安全方面有几个必须做的配置第一修改默认端口。8081 太常见容易被扫描。改成 8082 或别的。第二创建专用角色和用户。不要用 admin 账号做日常操作。创建一个npm-developer角色赋予nx-repository-view-npm-*-read和nx-repository-view-npm-*-add权限再创建一个用户绑定这个角色。这样开发者只能读写 npm 仓库不能碰其他配置。第三配置匿名访问。如果内网环境相对可信可以开启匿名读取权限这样开发者不需要登录就能安装包但发布包仍需认证。在Security Anonymous里勾选允许访问然后在Realms里激活npm Bearer Token Realm。第四启用 HTTPS。如果内网有证书颁发机构给 Nexus3 配一个证书。没有的话至少确保 HTTP 访问在内网范围内。Yarn 和 npm 都支持strict-sslfalse来跳过证书验证但这会降低安全性只建议在完全可控的内网使用。注意npm Bearer Token Realm必须激活否则npm login会失败。这个坑我踩过当时排查了半天才发现是 Realm 没开。3. 实操过程与核心环节实现3.1 联网机器上的资源准备找一台能访问外网的 Linux 或 macOS 机器开始准备离线资源。我习惯把所有东西放在一个目录里方便打包拷贝。mkdir -p /tmp/offline-pkg/{nexus,jdk,yarn,npm-cache} cd /tmp/offline-pkg下载 Nexus3 最新版。截至我写这篇内容时3.68 是稳定版但具体版本号你可以去官网查。下载地址格式是https://download.sonatype.com/nexus/3/nexus-3.x.x-xx-unix.tar.gz。wget https://download.sonatype.com/nexus/3/nexus-3.68.0-04-unix.tar.gz -O nexus/nexus.tar.gz下载 OpenJDK 11。Adoptium 的构建质量不错下载 tar.gz 版本。wget https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.22%2B7/OpenJDK11U-jdk_x64_linux_hotspot_11.0.22_7.tar.gz -O jdk/jdk11.tar.gz下载 Yarn。Yarn 1.x 和 2.x 配置方式不同这里以 Yarn 1.22 为例因为它在离线场景下更成熟。wget https://github.com/yarnpkg/yarn/releases/download/v1.22.22/yarn-v1.22.22.tar.gz -O yarn/yarn.tar.gz准备 npm 包缓存。新建一个临时项目把团队常用的包写进package.json然后配置离线镜像并安装。mkdir /tmp/offline-pkg/npm-cache/project cd /tmp/offline-pkg/npm-cache/project cat package.json EOF { name: offline-cache, version: 1.0.0, dependencies: { lodash: ^4.17.21, axios: ^1.6.0, vue: ^3.4.0, react: ^18.2.0, typescript: ^5.3.0, webpack: ^5.90.0, vite: ^5.0.0 } } EOF cat .yarnrc EOF yarn-offline-mirror ../mirror yarn-offline-mirror-pruning false EOF yarn install执行完后/tmp/offline-pkg/npm-cache/mirror目录里就是所有包的 tarball。这个目录可能有好几百兆取决于包的数量。3.2 内网服务器上的 Nexus3 部署把/tmp/offline-pkg整个目录打包通过物理介质拷贝到内网服务器。假设拷贝到/opt/offline-pkg。cd /opt/offline-pkg tar -xzf jdk/jdk11.tar.gz -C /opt/ tar -xzf nexus/nexus.tar.gz -C /opt/ tar -xzf yarn/yarn.tar.gz -C /opt/配置 Java 环境变量。编辑/etc/profile.d/java.shexport JAVA_HOME/opt/jdk-11.0.227 export PATH$JAVA_HOME/bin:$PATH执行source /etc/profile.d/java.sh生效。验证java -version输出正确。配置 Yarn 环境变量。编辑/etc/profile.d/yarn.shexport PATH/opt/yarn-v1.22.22/bin:$PATHNexus3 解压后目录名带版本号改个短名字方便管理mv /opt/nexus-3.68.0-04 /opt/nexus修改 Nexus3 配置。编辑/opt/nexus/etc/nexus-default.propertiesapplication-port8082 application-host0.0.0.0 nexus-context-path/修改运行用户。Nexus3 默认用nexus用户如果服务器上没有这个用户可以改成当前用户。编辑/opt/nexus/bin/nexus.rcrun_as_userroot注意用 root 运行不是最佳实践但在内网隔离环境且没有其他用户的情况下这是最省事的办法。如果有条件还是创建一个专用用户。启动 Nexus3/opt/nexus/bin/nexus start检查状态/opt/nexus/bin/nexus status查看日志确认启动成功tail -f /opt/sonatype-work/nexus3/log/nexus.log看到Started Sonatype Nexus字样就说明成功了。首次启动会比较慢因为要初始化数据库和索引。3.3 Nexus3 仓库创建与配置浏览器访问http://内网IP:8082用 admin 账号登录。首次登录会提示修改密码新密码建议用强密码并记录在团队密码管理工具里。创建 hosted 仓库。路径Settings Repositories Create repository npm (hosted)。Name:npm-hostedOnline: 勾选Storage: 默认 blob storeHosted Deployment policy:Allow redeployAllow redeploy很重要否则同一个版本号不能重复发布。团队内部开发时经常需要覆盖发布所以选这个。创建 proxy 仓库。路径Create repository npm (proxy)。Name:npm-proxyRemote storage:https://registry.npmjs.orgOnline: 勾选Storage: 默认如果内网完全隔离这个仓库创建后也无法拉取但保留着不影响。如果 Nexus3 能通过某种方式访问外网比如通过内网代理那这个仓库就能自动缓存外网包。创建 group 仓库。路径Create repository npm (group)。Name:npm-groupStorage: 默认Group Member repositories: 把npm-hosted和npm-proxy都移到右边创建完成后group 仓库的 URL 就是客户端要配置的 registry 地址。格式是http://内网IP:8082/repository/npm-group/。配置 Realm。路径Settings Security Realms把npm Bearer Token Realm从 Available 移到 Active。这一步不做npm login会报 401。创建角色和用户。路径Settings Security Roles Create role。Role ID:npm-devName:npm-devPrivileges: 添加nx-repository-view-npm-*-read、nx-repository-view-npm-*-add、nx-repository-view-npm-*-browse然后创建用户Settings Security Users Create user。ID:devuserPassword: 设置一个密码Roles: 勾选npm-dev3.4 客户端配置与验证在开发者的机器上配置 Yarn 使用内网 registry。编辑~/.yarnrcregistry http://内网IP:8082/repository/npm-group/如果 Nexus3 没有配 HTTPS还需要加strict-ssl false配置 npm 的话编辑~/.npmrcregistryhttp://内网IP:8082/repository/npm-group/ strict-sslfalse登录认证npm login --registryhttp://内网IP:8082/repository/npm-group/输入用户名devuser和密码。成功后会在~/.npmrc里生成一个 token。验证安装mkdir /tmp/test-project cd /tmp/test-project npm init -y npm install lodash如果 Nexus3 的 proxy 仓库能访问外网lodash 会被自动缓存。如果完全隔离需要先把 lodash 的 tarball 上传到 hosted 仓库。上传 tarball 到 hosted 仓库npm publish /opt/offline-pkg/npm-cache/mirror/lodash-4.17.21.tgz --registryhttp://内网IP:8082/repository/npm-hosted/批量上传的话写个循环for f in /opt/offline-pkg/npm-cache/mirror/*.tgz; do npm publish $f --registryhttp://内网IP:8082/repository/npm-hosted/ done实操心得批量上传时如果某个包已经存在且 deployment policy 不是 Allow redeploy会报错。可以加|| true忽略错误或者先检查包是否存在。3.5 私有包发布流程团队自己开发的包发布到npm-hosted仓库。在包的package.json里加{ publishConfig: { registry: http://内网IP:8082/repository/npm-hosted/ } }然后执行npm publish。发布成功后其他项目通过 group 仓库就能安装这个包。如果包有 scope比如myteam/utils需要在.npmrc里配置 scope 对应的 registrymyteam:registryhttp://内网IP:8082/repository/npm-group/这样安装myteam/utils时会走内网安装其他包也会走内网 group 仓库。4. 常见问题与排查技巧实录4.1 启动与连接类问题问题一Nexus3 启动后无法访问日志显示端口被占用。排查netstat -tlnp | grep 8082看谁占用了。如果是其他服务改 Nexus3 端口如果是 Nexus3 自己的残留进程kill掉再启动。问题二浏览器访问显示 503日志报Cannot allocate memory。Nexus3 默认 JVM 堆内存是 2GB如果服务器内存小需要调小。编辑/opt/nexus/bin/nexus.vmoptions-Xms512m -Xmx512m -XX:MaxDirectMemorySize1g问题三npm login报 401 Unauthorized。检查npm Bearer Token Realm是否激活。这是最常见的原因。另外确认用户名密码正确以及用户有nx-repository-view-npm-*-add权限。问题四yarn install报unable to verify the first certificate。Nexus3 用了自签名证书Yarn 不信任。在.yarnrc里加strict-ssl false。或者把证书导入系统信任链但内网环境一般直接关验证更省事。4.2 包管理与发布类问题问题五npm publish报 400 Bad Request提示Repository does not allow updating assets。hosted 仓库的 deployment policy 设成了Disable redeploy。改成Allow redeploy即可。问题六安装包时提示Package not found但 Nexus3 里明明有。检查客户端配置的 registry 地址是否指向 group 仓库。如果指向 hosted 仓库只能看到私有包看不到 proxy 缓存的包。另外确认 group 仓库的成员里包含了对应的 hosted 和 proxy 仓库。问题七Yarn 离线安装报Couldnt find package on npm registry。离线镜像目录里缺少对应的 tarball。检查yarn.lock里的版本号和镜像目录里的文件名是否匹配。Yarn 1.x 的镜像文件名格式是包名-版本号.tgzscope 包是scope-包名-版本号.tgz。问题八批量上传 tarball 时部分包失败。可能是包名里有特殊字符或者 tarball 本身损坏。先单独试一个失败的包看具体报错。如果是EPUBLISHCONFLICT说明包已存在忽略即可。4.3 性能与维护类问题问题九Nexus3 磁盘占用增长过快。proxy 仓库会缓存所有拉取过的包时间长了占用很大。定期清理Settings Repository Cleanup policies创建一个清理策略比如只保留最近 30 天未使用的缓存。然后应用到 proxy 仓库。问题十Yarn 安装速度慢。Nexus3 的 JVM 堆内存不足会导致响应慢。适当调大-Xmx比如 2GB 或 4GB。另外检查服务器磁盘 IONexus3 的 blob store 对磁盘随机读写要求较高机械硬盘会明显拖慢速度。问题十一如何备份 Nexus3 数据。需要备份两个目录/opt/nexus程序配置和/opt/sonatype-work/nexus3数据。数据目录里有 blob store、数据库、索引。备份时先停止 Nexus3拷贝完再启动。或者用 Nexus3 的导出功能在Settings System Tasks里创建一个Export databases任务。问题现象可能原因解决方向启动失败端口占用8081/8082 被占改端口或杀进程503 内存不足JVM 堆太小调 nexus.vmoptionsnpm login 401Realm 未激活激活 npm Bearer Token Realmpublish 400不允许覆盖改 deployment policy包找不到registry 指向错误指向 group 仓库离线安装失败镜像缺 tarball检查文件名和 lock磁盘增长快proxy 缓存堆积配 cleanup policy安装慢JVM 内存小/磁盘慢调内存/换 SSD4.4 独家避坑技巧第一个技巧Nexus3 的 admin 密码文件在首次登录后就删除了。如果你忘了改后的密码只能重置。重置方法是停止 Nexus3删除/opt/sonatype-work/nexus3/db/security目录重启后会重新生成admin.password文件。但这会清空所有用户和权限配置慎用。第二个技巧Yarn 的离线镜像目录不要提交到 Git。这个目录动辄几百兆提交上去仓库会爆炸。在.gitignore里加上npm-packages-offline-cache/。如果需要团队共享放到内网文件服务器上用软链接指向。第三个技巧Nexus3 的 group 仓库有缓存延迟。当你往 hosted 仓库发布一个新包后立即通过 group 仓库安装可能会找不到。等几秒再试或者直接指定 hosted 仓库地址安装一次触发索引更新。第四个技巧批量上传 tarball 时用--registry参数指定 hosted 仓库地址不要用 group 地址。group 仓库不允许 publish会报错。第五个技巧如果内网服务器时间不对Nexus3 的 token 会验证失败。确保服务器时间同步或者手动校准。date命令检查ntpdate同步如果内网有 NTP 服务器。这套方案我在两个团队落地过第一个团队规模小只用了 hosted 仓库手动上传了约 200 个常用包。第二个团队规模大Nexus3 能通过内网代理访问外网proxy 仓库自动缓存基本不需要手动干预。两种场景都跑得挺稳。关键是把初始配置做对后面就是日常维护的事了。