Nexus:依赖代理、内部制品与仓库恢复
Maven 下载一个 JAR 时,需要找到坐标对应的 POM、二进制及校验信息;npm 安装包时,需要取得包元数据和 tarball。Nexus Repository 把这些请求接到组织管理的仓库入口,保存内部发布的包,并缓存允许使用的上游依赖。团队因此可以统一认证、控制依赖来源,也能在上游短暂不可用时继续使用已缓存内容。
仓库里还保存着回退需要的旧制品。部署成功后立即清理旧版本,会让下一次故障恢复失去输入。
仓库类型决定发布和下载走哪条路
Hosted、proxy 与 group
CI 发布 ───────→ maven-releases(hosted)──┐
CI 开发快照 ───→ maven-snapshots(hosted)─┤
├← maven-public(group)← 构建下载
Maven Central ← maven-central(proxy)────┘
npm publish ──→ npm-internal(hosted)────┐
├← npm-public(group)← npm install
npm 上游 ←──── npm-upstream(proxy)──────┘hosted 保存由用户发布的内容;proxy 按需访问上游并缓存;group 按配置聚合成员仓库,向客户端提供统一读取入口。普通发布请求应发到对应 hosted,不能把 group 下载地址直接填进 Maven 的部署配置。具体格式的能力和限制见 Maven 仓库与 npm registry。
group 的成员和顺序需要明确。相同坐标如果同时出现在内部仓库和公共上游,消费者可能得到不同来源的内容;内部命名空间、允许访问的上游及路由规则要一起安排。仅把公司仓库排在最前,仍不足以完整处理 dependency confusion。
版本、坐标与字节
Maven 坐标由 groupId、artifactId、version,加上需要时的 classifier 和扩展名确定。正式版仓库选择 Release 策略并禁止重新部署;开发快照使用单独的 Snapshot 仓库,允许工具按快照规则更新元数据。将 SNAPSHOT 发布到只收 Release 的仓库,会在服务器端被拒绝。
npm 使用包名与版本标识发布内容,@team/name 中的 team 是 scope。正式版本发布后应保持内容稳定,修复通过新版本表达。Raw 仓库则按路径保存文件,适合交付 ZIP、JAR 和发布清单,但不会替应用设计版本目录,也不提供 Maven 的依赖语义。
制品路径负责定位,SHA-256 用于检查实际字节。发布清单还应保存源码提交、工具链和必要配置版本。摘要要从可信发布记录取得;下载完临时算一个值,只能建立本次文件的标识,无法独立判断来源是否可信。
数据库与 blob store
数据库保存仓库、组件、资产及权限等元数据,blob store 保存二进制内容。一个组件可能有多个资产,例如 JAR、POM 和附属文件。删除数据库之外的 blob 文件,会留下“列表可见、下载失败”的损坏状态;只复制 blob 目录,也无法完整重建原有权限和仓库关系。存储类型和迁移限制见 Blob Stores。
生产容量需要同时估计发布增长、代理缓存、保留窗口、数据库和备份空间。磁盘占用已经接近极限时,再临时运行大规模清理任务可能使 IO 和数据库压力一起上升。
在 Linux 建立实验实例并完成初始化
版本、身份与启动参数
下面使用 Linux、Bash、Docker Engine、curl 7.76 或更新版本;操作者有使用 Docker 的权限。镜像固定为 sonatype/nexus3:3.94.2,对应 3.94 系发行说明。这是固定实验基线,部署前仍需检查安全公告与组织支持版本。
实验容器默认使用内嵌 H2,适合本机理解仓库操作,不作为受支持的生产容器配置。官方当前要求明确限制 H2 的规模并不支持 H2 容器部署;正式容器部署应使用外部 PostgreSQL。Java、内存、数据库和文件系统条件以 系统要求为准。
在一台没有同名实验资源的主机执行:
docker volume create nexus-lab-data
docker run -d --name nexus-lab --restart unless-stopped \
--memory 4g \
-p 127.0.0.1:18083:8081 \
-v nexus-lab-data:/nexus-data \
-e 'INSTALL4J_ADD_VM_PARAMS=-Xms1200m -Xmx1200m -XX:MaxDirectMemorySize=1200m -Djava.util.prefs.userRoot=/nexus-data/javaprefs' \
sonatype/nexus3:3.94.2
docker exec nexus-lab id
docker logs --tail 80 nexus-lab
curl -q --noproxy '*' --fail-with-body --connect-timeout 3 --max-time 10 \
http://127.0.0.1:18083/service/rest/v1/status容器内 Nexus 使用 UID/GID 200:200,运行在镜像自带 Java 21 上。4 GiB 限额和缩小的 JVM 参数仅供低并发实验,生产要按负载重新分配堆、直接内存和系统余量。使用命名卷可避免直接把不适合数据库的宿主共享目录挂入容器;若使用 Linux bind mount,须提前为该精确目录设置正确属主,不能给整个数据盘开放所有用户写权限。
初始化可能需要几分钟。status 返回成功说明服务已进入可用状态;连接拒绝时先看启动日志和容器状态,内存不足则检查 docker inspect nexus-lab --format '{{.State.OOMKilled}}',不要连续重建并丢弃数据卷。镜像参数见 官方容器源码与说明。
首次登录与许可
浏览器打开 http://127.0.0.1:18083。远程 Linux 服务器保持回环监听,可通过 SSH 隧道访问,不必直接向公网开放管理端口。
管理员在自己的终端读取一次初始密码:
docker exec nexus-lab cat /nexus-data/admin.password以 admin 登录,立即换成独立强密码,完成首次设置,并关闭匿名访问。新版 Community Edition 在写入前要求完成许可确认;应由有权代表使用方的人阅读并确认。仅能登录页面、创建仓库,并不意味着首次写入已经允许;若上传返回包含 EULA 提示的 403,先回到初始化向导,不要把权限全部放开尝试绕过。
长期使用时,把管理入口置于 HTTPS 和管理网段,应用或流水线使用单独身份。正式仓库不应依赖所有开发者共用 admin 密码。
建立仓库与角色
在管理页 Repository → Repositories → Create repository 创建以下仓库。仓库名与后面的命令保持一致;默认已存在同名 Maven 仓库时,检查配置而非重复创建。
| 名称 | 格式和类型 | 关键字段 |
|---|---|---|
| maven-releases | maven2 hosted | Version policy=Release,Layout=Strict,Deployment policy=Disable redeploy |
| maven-snapshots | maven2 hosted | Snapshot,Strict,Allow redeploy |
| maven-central | maven2 proxy | Remote storage 指向 Maven Central,使用受信任 HTTPS 上游 |
| maven-public | maven2 group | 成员依次为 releases、snapshots、central |
| npm-internal | npm hosted | Disable redeploy |
| npm-upstream | npm proxy | Remote storage 指向 npm 官方 registry |
| npm-public | npm group | 成员为 npm-internal、npm-upstream |
| raw-releases | raw hosted | Disable redeploy |
各仓库选实验用 default blob store,并保持 Online。上游地址分别是 https://repo.maven.apache.org/maven2/ 和 https://registry.npmjs.org/;前者路径末尾的 /maven2/ 属于仓库根路径,不能省略成网站首页。
在 Security → Roles 创建 publisher 和 reader 角色,再在 Users 创建同名实验用户并设独立密码。对指定仓库授予 nx-repository-view-<format>-<repository>-<action>:
- reader:下载涉及的 group 与成员仓库授予 browse、read。
- publisher:在允许发布的 hosted 上增加 add、edit;Maven 需要更新元数据,不能简单只授 add。发布工作需要读取已有元数据时也保留 browse、read。
- 不授 delete、仓库管理或系统管理员权限。仓库 write policy 继续限制正式内容被覆盖,edit 权限本身不能替代版本策略。
实验身份可覆盖表中三种格式;生产按项目拆角色或使用内容选择器约束路径。启用的 realm、令牌与身份源要按客户端选择,npm login 的 Bearer Token Realm 在后面单独配置。
正式实例接外部 PostgreSQL
新实例应在首次启动前完成数据库配置。先在受支持 PostgreSQL 中创建专用数据库与属主账号,安装 pg_trgm,核对编码、schema 和低延迟网络,再选择一种连接配置方式。以下是容器环境变量的字段形式:
NEXUS_DATASTORE_NEXUS_JDBCURL=jdbc:postgresql://postgres.internal:5432/nexus
NEXUS_DATASTORE_NEXUS_USERNAME=nexus
NEXUS_DATASTORE_NEXUS_PASSWORD=<由凭据系统注入>密码占位符不能原样启动。环境变量对有容器管理权限的人可见,需要限制管理接口和配置文件权限;跨主机连接还需按数据库 TLS 配置校验证书。环境变量、JVM 参数和属性文件有优先顺序,混用容易让修改落在未生效的位置,具体步骤见 PostgreSQL 安装配置。
已有 H2 数据不能靠补几项环境变量自动迁过去,应走目标版本支持的迁移流程。HA 也需要对应许可、共享存储和数据库部署方式;多个实例直接共用同一个 H2 卷会破坏数据。
让 Maven、npm 和发布程序真正使用仓库
先用 Raw 检查写入、下载和拒绝
完成许可初始化和角色配置后,在同一 Linux 主机的普通用户终端执行。curl 只给用户名时会交互询问密码,避免把明文密码放入命令历史。所有请求绕过本机代理与 curlrc,实验使用回环 HTTP;远程正式入口必须换成可信 HTTPS。
LAB=$(mktemp -d)
cd "$LAB"
NEXUS_URL=http://127.0.0.1:18083
printf 'release=1.0.0\n' > release.txt
sha256sum release.txt
curl -q --noproxy '*' --fail-with-body --user publisher \
--upload-file release.txt \
"$NEXUS_URL/repository/raw-releases/orders/1.0.0/release.txt"
curl -q --noproxy '*' --fail-with-body --user reader \
--output received.txt \
"$NEXUS_URL/repository/raw-releases/orders/1.0.0/release.txt"
cmp release.txt received.txt首次 PUT 应成功,下载结果与原文件完全一致,cmp 成功时无输出。随后重传同一路径,应因 Disable redeploy 被拒绝;reader 向一个新路径上传,也应被拒绝:
STATUS=$(curl -q --noproxy '*' --silent --show-error --user publisher \
--upload-file release.txt --output redeploy-error.txt --write-out '%{http_code}' \
"$NEXUS_URL/repository/raw-releases/orders/1.0.0/release.txt")
test "$STATUS" = 400
STATUS=$(curl -q --noproxy '*' --silent --show-error --user reader \
--upload-file release.txt --output permission-error.txt --write-out '%{http_code}' \
"$NEXUS_URL/repository/raw-releases/orders/1.0.0/forbidden.txt")
test "$STATUS" = 403失败实验不使用 --fail-with-body 提前丢失状态判断,而是保存响应体并检查明确状态。若第一轮合法上传都不成功,不能拿后面的 403 宣称只读权限已经正确;先区分许可、认证、角色和仓库策略。
Maven:mirror 负责消费,distributionManagement 负责发布
使用已安装 JDK 21/25、Maven 3.9 的项目,或承接 构建工具与制品中的工程。为实验单独创建 settings.xml,不覆盖现有用户配置;其内容如下:
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0">
<mirrors>
<mirror>
<id>nexus-read</id>
<mirrorOf>*</mirrorOf>
<url>http://127.0.0.1:18083/repository/maven-public/</url>
</mirror>
</mirrors>
<servers>
<server>
<id>nexus-read</id>
<username>${env.NEXUS_READ_USER}</username>
<password>${env.NEXUS_READ_PASSWORD}</password>
</server>
<server>
<id>nexus-release</id>
<username>${env.NEXUS_PUBLISH_USER}</username>
<password>${env.NEXUS_PUBLISH_PASSWORD}</password>
</server>
<server>
<id>nexus-snapshot</id>
<username>${env.NEXUS_PUBLISH_USER}</username>
<password>${env.NEXUS_PUBLISH_PASSWORD}</password>
</server>
</servers>
</settings>Maven 根据最终 repository/mirror 的 id 查找 server 凭据,id 不是用户名,也不要求等于 Nexus 仓库名。上例下载使用 nexus-read,所以只写一个 id 为 maven-public 的 server 不会自动匹配。mirrorOf=* 还会覆盖其他下载仓库,组织必须保证 group 能提供项目所需依赖和插件;匹配细节见 Maven 镜像配置。
项目 pom.xml 中加入发布目标,坐标版本使用新的实验版本:
<distributionManagement>
<repository>
<id>nexus-release</id>
<url>http://127.0.0.1:18083/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>nexus-snapshot</id>
<url>http://127.0.0.1:18083/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>普通终端交互输入两个用户的密码;CI 使用秘密变量注入,不运行 set -x:
export NEXUS_READ_USER=reader NEXUS_PUBLISH_USER=publisher
read -rsp 'Reader password: ' NEXUS_READ_PASSWORD; printf '\n'
read -rsp 'Publisher password: ' NEXUS_PUBLISH_PASSWORD; printf '\n'
export NEXUS_READ_PASSWORD NEXUS_PUBLISH_PASSWORD
chmod 600 settings.xml
mvn -s settings.xml -B -Dmaven.repo.local="$LAB/m2-publish" clean deploy
unset NEXUS_READ_PASSWORD NEXUS_PUBLISH_PASSWORDdeploy 会先走 Maven 生命周期,再上传 JAR、POM 和相关元数据。预期日志中的上传目标是 hosted,下载目标是 group。发布失败时检查整个坐标,正式版本已经部分上传也可能阻止重试;先调查已有资产,选择新版本或经过授权的清理,不能临时开启正式仓库覆盖来掩盖失败。发布参数见 Maven Deploy Plugin。
验证消费时,在依赖该新坐标的另一个工程使用新的 maven.repo.local 执行 verify,并检查 group 下载记录。若沿用发布机的本地缓存,即使仓库地址错了也可能构建成功。第三方 JAR 可使用 deploy-file,但应同时提供真实 POM、来源、许可与校验信息;只有一个文件名无法补齐传递依赖。
npm:scope、认证路径和发布入口
以 Node.js 24 和 npm 11 为例。在 Nexus Security → Realms 启用 npm Bearer Token Realm,然后准备专用用户配置和一个空目录。以下命令会交互询问 publisher 的用户名、密码等登录信息:
mkdir npm-package
cd npm-package
export NPM_CONFIG_USERCONFIG="$LAB/npm-user.npmrc"
npm config set registry "$NEXUS_URL/repository/npm-public/" --location=user
npm login --auth-type=legacy --registry="$NEXUS_URL/repository/npm-internal/"
chmod 600 "$NPM_CONFIG_USERCONFIG"
npm init -y
npm pkg set name=@ops5/message version=1.0.0
printf 'module.exports = "hello from Nexus";\n' > index.js
npm publish --registry="$NEXUS_URL/repository/npm-internal/" --ignore-scriptsnpm publish 明确选 hosted,避免默认 registry 把内部包发到公共站点。登录生成的 token 会限定到对应 registry 路径;同一主机的 /repository/npm-internal/ 与 /repository/npm-public/ 是两个认证范围。使用 scope 配置时也必须保持 registry 与 token 路径对应,规则见 npmrc。
换到独立消费目录,用 reader 登录 group,再下载刚才的新版本:
cd "$LAB"
mkdir npm-consumer
cd npm-consumer
npm login --auth-type=legacy --registry="$NEXUS_URL/repository/npm-public/"
npm init -y
npm install @ops5/message@1.0.0 --cache "$LAB/npm-clean-cache" \
--ignore-scripts --no-audit --no-fund
node -e 'console.log(require("@ops5/message"))'
unset NPM_CONFIG_USERCONFIG预期输出 hello from Nexus。新 cache 排除了旧包缓存;--ignore-scripts 对这个无安装脚本的小包足够,真实项目若依赖原生模块构建,必须单独评估允许执行的脚本,不能把安装成功和应用可用混在一起。
package-lock.json 的 resolved 地址可能仍指向旧 registry。迁移仓库时审查锁文件、重新生成或按团队规则更新,再用空缓存执行 npm ci。Playwright、node-gyp 等工具还可能另外下载浏览器或头文件;配置 npm registry 不会自动代理这些二进制地址。
上游缓存与离线构建
proxy 只缓存请求过的内容。某个开发机成功构建后断网,另一种 CPU 平台、另一套 profile 或新增插件仍可能缺包。离线准备应按实际目标平台、构建命令和依赖集合预热,再在没有上游出口、没有本地缓存的环境重新构建。
Nexus 负缓存可能暂存“上游不存在”的结果,Maven 也可能记录失败下载,npm 则有本地内容缓存。排查时逐层确认:仓库 Browse 能否看到资产,直接 hosted 能否下载,group 是否包含该成员,proxy 是否被自动阻断或仍持有负缓存。只在确定影响范围后对相应 proxy 失效缓存;反复清空所有缓存会把请求洪峰推给上游。
清理、备份与故障恢复
清理先按仓库职责决定保留规则
正式 hosted 保留当前生产版本、回退窗口内版本及受保留要求约束的制品;Snapshot 可按开发节奏缩短保留;proxy 缓存通常可以重新获取,但离线环境和已下架上游不能依赖这个假设。
在管理页配置 Cleanup Policy,先检查条件及预览结果,再把策略关联到目标仓库,安排低峰执行对应清理任务。组件删除后,blob 的物理空间回收可能还需 blob store 的 compact 任务;观察任务结果与磁盘实际变化。不要进入 blob 目录按文件修改时间手工删除。任务可用性与具体存储行为以对应版本的 Blob Stores 文档和管理页为准。
同一恢复点保存数据库、blob 与配置
备份需要包含数据库、全部 blob store、实例配置、加密材料和版本信息。外部 PostgreSQL 的备份不能只靠复制 Nexus 数据卷完成;云对象存储也不会随本机 tar 自动进入归档。官方说明下载请求也可能更新数据库元数据,因此维护时仅禁止上传仍未必停止写入,见 备份与恢复。
对前面的低并发 H2 实验,可用停机归档理解一致恢复。普通 Docker 操作者执行,归档目录用新临时目录,不覆盖任何旧备份:
BACKUP=$(mktemp -d)
docker stop --time 120 nexus-lab
test "$(docker inspect nexus-lab --format '{{.State.Running}}')" = false
docker run --rm --user 0:0 \
-v nexus-lab-data:/source:ro -v "$BACKUP:/backup" \
alpine:3.22 sh -c 'tar -czf /backup/nexus-data.tgz -C /source .'
sudo sha256sum "$BACKUP/nexus-data.tgz"
docker start nexus-lab备份辅助容器用 root 读取全部文件并保留属主;Nexus 服务自身仍用 200。归档可能包含凭据和密钥,应转移到受限、加密的备份位置。先确认 tar 成功再记录本次备份;失败归档不能当作可恢复副本。
恢复验证使用全新卷与不同回环端口,原实例和原卷保持不动。先确认 nexus-restore-data 未被使用,再执行:
docker volume create nexus-restore-data
docker run --rm --user 0:0 \
-v nexus-restore-data:/restore -v "$BACKUP:/backup:ro" \
alpine:3.22 sh -c 'tar -xzf /backup/nexus-data.tgz -C /restore'
docker run -d --name nexus-restore --memory 4g \
-p 127.0.0.1:18084:8081 -v nexus-restore-data:/nexus-data \
-e 'INSTALL4J_ADD_VM_PARAMS=-Xms1200m -Xmx1200m -XX:MaxDirectMemorySize=1200m -Djava.util.prefs.userRoot=/nexus-data/javaprefs' \
sonatype/nexus3:3.94.2恢复后用原 reader 身份从 18084 下载 release.txt,与备份前的可信摘要比对,再验证 Maven/npm 包、权限拒绝和仓库配置。生产恢复环境还应隔离出站任务和原上游,避免恢复出的调度任务直接开始工作。
外部 PostgreSQL 采用数据库原生备份/PITR,blob 使用相容的快照或版本化方案,并把两者协调到一致恢复点。升级前先在隔离环境恢复原版本,按发行说明执行升级,再测试客户端。数据库已被新版迁移后,不应直接让旧镜像接管该数据库;回退通常需要旧程序和升级前数据一起恢复。
根据请求落在哪一层排障
| 现象 | 检查位置 | 下一步 |
|---|---|---|
| 401 | mirror/server id、npm 认证路径、token 是否过期 | 用同一身份请求明确仓库 URL,再修客户端匹配 |
| 403 | 响应体、首次许可、角色与仓库权限 | 先排除初始化未完成,再检查该路径允许的动作 |
| 400 发布失败 | Release/Snapshot 策略、重复版本、布局与格式 | 核对坐标与已有资产,按新版本或批准清理处理 |
| 404 但 hosted 有包 | group 成员、代理负缓存、客户端旧地址 | 比较 hosted 与 group 请求,定点失效缓存 |
| 下载慢或 502 | Nexus 日志、数据库延迟、blob IO、代理连接 | 分清客户端→Nexus 与 Nexus→上游,避免盲目增大超时 |
| 页面正常而某包损坏 | 资产元数据、对应 blob、下载摘要 | 停止传播该包,从可信备份或发布方恢复 |
| 启动后只读/写失败 | 磁盘余量、数据库连接、许可状态 | 先保全日志和数据,处理容量或连接再验证合法写入 |
日常观察请求错误率、响应延迟、上游可达性、数据库连接、磁盘增长和清理任务时长。一次成功下载用于证明这条读取路径当前可用;长期运行还需要定期抽样恢复,让备份中的内容确实能重新服务构建。
