Nexus Repository 3 是一套自托管制品库:它可以把 Maven Central、npm Registry 等上游依赖缓存在自己的 VPS,也可以保存团队内部发布的 JAR、npm 包和其他构建产物。对有 Jenkins、GitHub Actions 或多台开发机的团队来说,它解决的不是“哪里放一个压缩包”,而是依赖下载速度、上游限流、私有包权限、构建可复现与制品生命周期管理。
本文以 Ubuntu 24.04、Docker Compose、Nexus Repository Community Edition 3.96.0、PostgreSQL 和 Caddy 为例,搭建一套适合小团队长期运行的 HTTPS 私有制品库。你将完成 Maven 与 npm 仓库配置、最小权限账号、CI 接入、监控、备份恢复和可回滚升级。
版本提示:截至 2026 年 9 月,Sonatype 官方 Docker Hub 的当前固定版本为
3.96.0,默认镜像已从 3.91.0 起采用 Alpine 基础镜像。本文固定3.96.0-alpine,上线前请先阅读对应版本的 release notes,不要直接用latest做无人值守升级。
以下场景通常值得部署:
- 多个项目反复下载 Maven、npm 依赖,希望减少公网下载和上游波动;
- 需要发布公司内部 JAR、父 POM、前端组件或 CLI 包;
- Jenkins、GitHub Actions、自建 Runner 需要统一的制品入口;
- 希望对发布、下载、删除分别授权,并保留审计边界;
- 已经有 Jenkins CI/CD、Harbor 私有镜像仓库 或 SonarQube 代码质量平台,准备补齐 DevOps 制品链路。
如果只有一个人、一个项目,且依赖缓存命中率很低,直接使用云厂商制品库可能更省运维时间。Nexus 的真正价值来自多个客户端共享缓存、统一私有包和集中权限;它不是对象存储的替代品,也不应被当作普通网盘。
三者解决的问题不同:
| 系统 | 主要保存内容 | 典型客户端 | 是否适合替代 Nexus |
|---|---|---|---|
| Nexus Repository | Maven、npm、PyPI、NuGet、Raw 等制品与代理缓存 | Maven、Gradle、npm、CI | 本文目标 |
| Harbor | OCI/Docker 镜像、签名与漏洞扫描 | Docker、Podman、Kubernetes | 只做镜像时更专业 |
| Gitea/GitLab | Git 源代码、Issue、合并请求 | Git、IDE、CI | 不适合作为通用制品代理 |
| S3/对象存储 | 文件对象 | SDK、rclone、应用程序 | 缺少包协议、索引和代理语义 |
如果主要需求是 Docker 镜像扫描、复制和项目隔离,继续使用 Harbor;如果还要统一 Maven、npm、PyPI 等多种包格式,Nexus 更合适。两者可以并存:Harbor 管 OCI 镜像,Nexus 管语言生态制品。
Sonatype 当前给“小型”部署的官方基线是 2 vCPU、8 GB RAM、20 GB 本地 blob 存储,并指出性能通常更受磁盘 I/O 和网络影响。Nexus 还要求为系统和文件缓存保留约三分之一内存,因此 2 GB 或 4 GB VPS 即使短暂启动,也不等于适合生产。
| 用途 | 建议配置 | 数据库 | 说明 |
|---|---|---|---|
| 个人短期试用 | 2 vCPU / 4 GB RAM / 40 GB SSD | H2 | 仅做功能验证,不作为本文生产方案 |
| 小团队生产 | 2–4 vCPU / 8 GB RAM / 100 GB NVMe 起 | PostgreSQL | 本文推荐起点,按制品增长留空间 |
| 高频 CI | 4+ vCPU / 16 GB RAM / 200 GB+ NVMe | 独立 PostgreSQL | 需要监控 IOPS、缓存命中和并发 |
| 大规模或高可用 | 按官方参考架构 | 外部 PostgreSQL | 不应靠单台 VPS 拼 HA |
磁盘容量要按“现有私有制品 + 代理缓存增长 + 备份副本 + 升级临时空间”计算。官方明确要求始终至少保留 4 GB 可用空间;低于阈值时数据库可能切换为只读。Maven 和容器镜像仓库增长到数百 GB 并不罕见,因此建议把 70% 使用率设为预警线,而不是等磁盘写满。
新实例第一次启动会默认使用内嵌 H2,但官方把 H2 定位在最多 20 万请求/天或 10 万组件的范围,并明确说明 H2 的容器化部署不受支持;生产环境推荐外部 PostgreSQL。因此本文从第一次启动就接 PostgreSQL,避免日后再迁移数据库。
PostgreSQL 与 Nexus 应尽量位于同一台 VPS 或同一区域的低延迟网络中。数据库用户必须拥有数据库,升级时的 schema 变更需要 owner 权限;此外必须安装 pg_trgm 扩展。
准备一个独立域名,例如 repo.example.com,把 A/AAAA 记录解析到 VPS。防火墙只开放:
22/tcp:SSH,最好限制管理 IP;80/tcp:Caddy 申请证书和 HTTP 跳转;443/tcp:Nexus Web UI 与包客户端;8081/tcp:只在 Docker 内网使用,不对公网开放;5432/tcp:只在 Docker 内网使用,不对公网开放。
先检查解析:
export NEXUS_DOMAIN="repo.example.com"
getent ahosts "$NEXUS_DOMAIN"
curl -4 https://ifconfig.me
如果域名还指向旧 IP,先修复 DNS;不要在公网暴露 8081 或 PostgreSQL 来“临时绕过”反向代理。
下面使用 Docker 官方 apt 仓库。若服务器已经安装并验证过 Docker,可跳过本节。
sudo apt-get update
sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
sudo docker version
sudo docker compose version
项目放在 /opt/nexus。Nexus 官方容器以内置 UID 200 写入 /nexus-data,这里使用 Docker named volume,避免宿主机 bind mount 的权限错误。
sudo install -d -m 0750 /opt/nexus
cd /opt/nexus
umask 077
openssl rand -base64 36 | tr -d '\n' | sudo tee /opt/nexus/.postgres-password >/dev/null
sudo chmod 600 /opt/nexus/.postgres-password
创建 /opt/nexus/.env,把示例域名换成真实域名;POSTGRES_PASSWORD 的值来自刚生成的文件。
cd /opt/nexus
POSTGRES_PASSWORD_VALUE="$(sudo cat /opt/nexus/.postgres-password)"
sudo tee /opt/nexus/.env >/dev/null <<EOF
NEXUS_VERSION=3.96.0-alpine
NEXUS_DOMAIN=repo.example.com
POSTGRES_DB=nexus
POSTGRES_USER=nexus
POSTGRES_PASSWORD=${POSTGRES_PASSWORD_VALUE}
EOF
sudo chmod 600 /opt/nexus/.env
unset POSTGRES_PASSWORD_VALUE
不要把 .env、数据库备份或初始管理员密码提交到 Git。若密码中包含特殊字符,不要把它直接拼进 JDBC URL;本文把用户名、密码和 URL 分开传递。
创建 initdb/01-nexus.sql,让数据库 owner 创建独立 schema,并在同一 schema 安装 pg_trgm:
cd /opt/nexus
sudo install -d -m 0750 initdb
sudo tee initdb/01-nexus.sql >/dev/null <<'SQL'
CREATE SCHEMA IF NOT EXISTS nexus AUTHORIZATION nexus;
CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA nexus;
SQL
sudo chmod 640 initdb/01-nexus.sql
初始化脚本只会在 PostgreSQL 数据卷首次为空时运行。如果已经错误初始化过,不要直接删除生产卷;应先确认数据和备份,再按迁移方案处理。
创建 /opt/nexus/compose.yaml:
name: nexus-repository
services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
- ./initdb/01-nexus.sql:/docker-entrypoint-initdb.d/01-nexus.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 12
start_period: 20s
networks:
- backend
nexus:
image: sonatype/nexus3:${NEXUS_VERSION}
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
NEXUS_DATASTORE_NEXUS_JDBCURL: jdbc:postgresql://postgres:5432/nexus?currentSchema=nexus
NEXUS_DATASTORE_NEXUS_USERNAME: ${POSTGRES_USER}
NEXUS_DATASTORE_NEXUS_PASSWORD: ${POSTGRES_PASSWORD}
INSTALL4J_ADD_VM_PARAMS: >-
-Xms4096m -Xmx4096m -XX:MaxDirectMemorySize=4096m
-Djava.util.prefs.userRoot=/nexus-data/javaprefs
-Dnetworkaddress.cache.ttl=5
volumes:
- nexus-data:/nexus-data
expose:
- "8081"
stop_grace_period: 2m
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8081/service/rest/v1/status >/dev/null || exit 1"]
interval: 20s
timeout: 10s
retries: 30
start_period: 180s
networks:
- backend
- proxy
caddy:
image: caddy:2-alpine
restart: unless-stopped
depends_on:
nexus:
condition: service_healthy
environment:
NEXUS_DOMAIN: ${NEXUS_DOMAIN}
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
networks:
- proxy
networks:
backend:
internal: true
proxy:
volumes:
postgres-data:
nexus-data:
caddy-data:
caddy-config:
这份 Compose 有几个关键点:
- Nexus 固定版本,而不是随重启漂移的
latest; - 8081 和 5432 没有映射到宿主机;
- PostgreSQL 必须健康后才启动 Nexus;
stop_grace_period: 2m对应官方建议的 120 秒优雅停止;- 从 3.78 起,Docker 镜像的 JVM 参数应通过
INSTALL4J_ADD_VM_PARAMS配置; - Java preferences 放到持久化目录,防止容器重建后丢失相关状态。
如果 VPS 只有 8 GB 内存,给 Nexus 约 4 GB 堆与 4 GB direct memory 是偏积极的上限,需要结合 PostgreSQL、Caddy 和系统余量观察;若发生 OOM,应先看实际指标,而不是盲目继续增大参数。
创建 /opt/nexus/Caddyfile:
{$NEXUS_DOMAIN} {
encode zstd gzip
reverse_proxy nexus:8081 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
transport http {
read_timeout 300s
write_timeout 300s
}
}
log {
output stdout
format json
}
}
Sonatype 建议在反向代理后的 Nexus 中保留 HTTP Forwarded Headers capability。Caddy 会终止 TLS,并向 Nexus 传递 Host、客户端 IP 和协议。长超时用于大制品上传,但真正的上传上限还要结合客户端、磁盘和业务策略控制。
验证配置展开结果:
cd /opt/nexus
sudo docker compose --env-file .env config >/tmp/nexus-compose.rendered.yaml
sudo docker run --rm -v /opt/nexus/Caddyfile:/etc/caddy/Caddyfile:ro caddy:2-alpine caddy validate --config /etc/caddy/Caddyfile
cd /opt/nexus
sudo docker compose --env-file .env pull
sudo docker compose --env-file .env up -d
sudo docker compose --env-file .env ps
sudo docker compose --env-file .env logs --tail=200 nexus
首次启动通常需要数分钟。不要因为 30 秒内没有页面就反复重启;观察日志和 health 状态:
cd /opt/nexus
sudo docker compose --env-file .env exec postgres pg_isready -U nexus -d nexus
sudo docker compose --env-file .env exec postgres psql -U nexus -d nexus -c '\dx'
curl -fsS https://repo.example.com/service/rest/v1/status
curl -fsSI https://repo.example.com/
\dx 输出中应出现 pg_trgm。若公网 HTTPS 失败,先检查 DNS、80/443 防火墙和 Caddy 日志:
cd /opt/nexus
sudo docker compose --env-file .env logs --tail=200 caddy
sudo ss -lntup | grep -E ':(80|443)\b'
官方镜像会把一次性管理员密码写入 /nexus-data/admin.password:
cd /opt/nexus
sudo docker compose --env-file .env exec nexus cat /nexus-data/admin.password
访问 https://repo.example.com,用用户名 admin 登录,立即完成:
- 修改管理员密码,并存入密码管理器;
- 根据实际需求决定是否允许匿名读取;
- 建立个人管理员账户,避免日常共享
admin; - Settings → Repository → Data Store 确认 JDBC URL 指向 PostgreSQL,而不是 H2;
- Settings → System → Capabilities 确认 HTTP Forwarded Headers capability 处于启用状态;
- 设置邮件服务器和告警接收人。
初始密码文件在完成向导后可能被移除,这是正常现象。不要把密码截图、日志或 CI 输出发到工单和群聊。
Nexus 的核心不是“建一个仓库”,而是组合三种仓库类型:
hosted:保存团队自己发布的包;proxy:代理并缓存远程上游;group:把多个 hosted/proxy 合并成客户端的单一入口。
推荐约定:客户端下载统一走 group,内部发布只写 hosted,proxy 只负责上游缓存。这样以后更换上游、增加镜像源或调整缓存策略时,不必修改所有项目。
默认安装通常已有:
maven-central:代理 Maven Central;maven-releases:保存正式版本;maven-snapshots:保存 SNAPSHOT;maven-public:组合上述仓库,作为下载入口。
在 Settings → Repository → Repositories 检查这些仓库的 Blob Store、Version Policy 和 Deployment Policy。不要允许正式版本被随意覆盖;SNAPSHOT 与 release 应分开。
开发机的 ~/.m2/settings.xml 可写成:
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0">
<mirrors>
<mirror>
<id>nexus</id>
<mirrorOf>*</mirrorOf>
<url>https://repo.example.com/repository/maven-public/</url>
</mirror>
</mirrors>
<servers>
<server>
<id>nexus-releases</id>
<username>${env.NEXUS_USERNAME}</username>
<password>${env.NEXUS_PASSWORD}</password>
</server>
<server>
<id>nexus-snapshots</id>
<username>${env.NEXUS_USERNAME}</username>
<password>${env.NEXUS_PASSWORD}</password>
</server>
</servers>
</settings>
项目 pom.xml 的发布目标:
<distributionManagement>
<repository>
<id>nexus-releases</id>
<url>https://repo.example.com/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>nexus-snapshots</id>
<url>https://repo.example.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
验证下载是否经过 Nexus:
export NEXUS_USERNAME="ci-reader"
export NEXUS_PASSWORD="replace-with-secret"
mvn -U -s "$HOME/.m2/settings.xml" dependency:go-offline
CI 中应把账号密码放进 secret store,不要写进仓库里的 settings.xml。
在 UI 中创建:
npm-hosted:内部 npm 包;npm-proxy:Remote storage 指向https://registry.npmjs.org;npm-all:成员依次包含npm-hosted与npm-proxy。
下载统一指向 group:
npm config set registry "https://repo.example.com/repository/npm-all/"
npm config get registry
npm --loglevel info view lodash version
发布内部包时,建议在 package.json 明确 hosted 地址:
{
"name": "@example/internal-ui",
"version": "1.0.0",
"publishConfig": {
"registry": "https://repo.example.com/repository/npm-hosted/"
}
}
需要认证时,先在 Settings → Security → Realms 启用 npm Bearer Token Realm,然后使用现有 Nexus 用户登录。npm 9 及更高版本按官方说明需要 legacy auth:
npm adduser --auth-type=legacy --registry="https://repo.example.com/repository/npm-hosted/"
npm whoami --registry="https://repo.example.com/repository/npm-hosted/"
npm publish
不要在命令历史中直接写明文 token。CI 中可以动态生成临时 .npmrc,任务结束后删除:
set -euo pipefail
umask 077
cat >.npmrc <<EOF
registry=https://repo.example.com/repository/npm-all/
//repo.example.com/repository/npm-all/:_authToken=${NEXUS_NPM_TOKEN}
always-auth=true
EOF
npm ci
rm -f .npmrc
不要让 Jenkins 使用管理员账户。按用途拆分:
ci-reader:只读 group 仓库;ci-maven-publisher:读取maven-public,写入 releases/snapshots;ci-npm-publisher:读取npm-all,写入npm-hosted;- 运维管理员:管理仓库、任务和安全设置,不用于构建。
在 Settings → Security → Roles 中只授予目标仓库的 browse、read、add、edit 权限;不要给发布账号 delete,除非流程确实需要。Nexus 权限名称通常以 nx-repository-view-<format>-<repository>-<action> 组织,创建角色时逐项核对仓库名。
如果企业版启用 User Token,优先在 CI 中使用可轮换 token,并设置到期策略。每个流水线或团队单独使用凭据,可以在泄露时只吊销一个边界。
在 Jenkins Credentials 中创建:
nexus-maven:Username with password;nexus-npm-token:Secret text。
Maven Pipeline 可以临时生成 settings 文件:
pipeline {
agent any
stages {
stage('Build and publish') {
steps {
withCredentials([usernamePassword(
credentialsId: 'nexus-maven',
usernameVariable: 'NEXUS_USERNAME',
passwordVariable: 'NEXUS_PASSWORD'
)]) {
sh '''
set -eu
umask 077
envsubst < ci/settings.xml.tpl > ci/settings.xml
mvn -B -s ci/settings.xml clean deploy
rm -f ci/settings.xml
'''
}
}
}
}
}
流水线里不要 set -x,否则凭据可能出现在控制台日志。若还没有 CI 主机,可先按站内的 Jenkins VPS 部署教程 完成 HTTPS、Webhook 和 Agent 隔离。
在仓库的 Actions secrets 中创建 NEXUS_USERNAME 和 NEXUS_PASSWORD,再让工作流生成临时 settings.xml。下面假设项目已经准备好 ci/settings.xml.tpl,且模板只通过环境变量引用凭据:
name: publish-maven
on:
push:
tags:
- "v*"
permissions:
contents: read
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
cache: maven
- name: Publish to Nexus
env:
NEXUS_USERNAME: ${{ secrets.NEXUS_USERNAME }}
NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }}
run: |
set -euo pipefail
umask 077
envsubst < ci/settings.xml.tpl > ci/settings.xml
mvn -B -s ci/settings.xml clean deploy
rm -f ci/settings.xml
若 GitHub 托管 Runner 无法访问只开放在内网的 Nexus,应使用同网络的 self-hosted Runner、VPN 或零信任访问层,不要为了 CI 方便把管理接口和数据库暴露公网。第三方 Actions 应固定到经过审计的 commit SHA;上例用 major tag 便于阅读,上线时应按团队供应链策略固定版本。
代理缓存会持续增长。建议为不同内容设置独立 blob store 或至少独立清理策略:
- Maven snapshots:按最后下载时间和版本保留期清理;
- npm proxy:清理长期未下载的缓存组件;
- hosted releases:默认不要自动删除,建立审批流程;
- 删除后按计划运行 Compact blob store,才会真正回收空间;
- 大规模清理前先做可恢复备份,并在低峰期运行。
清理策略不是备份。误删的 hosted 制品不能靠重新代理上游恢复;代理缓存可以重建,内部制品则必须从备份或构建来源恢复。
最小监控应覆盖:
- HTTPS 与
/service/rest/v1/status可用性; - 容器重启次数、健康状态和 OOM;
- VPS 内存、load、磁盘使用率、inode 与 I/O latency;
- PostgreSQL 连接数、慢查询、autovacuum 和备份结果;
- Nexus 任务失败、blob 增长和登录异常;
- Caddy 证书续期与 4xx/5xx 比例。
可先做一个简单巡检:
set -euo pipefail
cd /opt/nexus
curl -fsS --max-time 15 https://repo.example.com/service/rest/v1/status >/dev/null
sudo docker compose --env-file .env ps
df -h /var/lib/docker
df -i /var/lib/docker
sudo docker stats --no-stream
生产环境应把结果交给监控系统,而不是依赖人工执行。70% 磁盘告警、80% 严重告警通常比“只剩 4 GB”更有操作空间。
Nexus 的 PostgreSQL 保存元数据,blob store 保存真实制品;只备份其中一个无法可靠恢复。官方还要求保留 $data-dir/keystores/node/ 下的节点身份。强一致备份最简单的方式是在维护窗口停止 Nexus,然后备份 PostgreSQL 与 Nexus 数据卷。
创建 /opt/nexus/backup.sh:
#!/usr/bin/env bash
set -euo pipefail
cd /opt/nexus
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
BACKUP_ROOT="/srv/backups/nexus/${STAMP}"
sudo install -d -m 0700 "$BACKUP_ROOT"
sudo docker compose --env-file .env stop -t 120 nexus
sudo docker compose --env-file .env exec -T postgres \
pg_dump -U nexus -d nexus -Fc >"${BACKUP_ROOT}/nexus-postgres.dump"
NEXUS_VOLUME="$(sudo docker volume ls --format '{{.Name}}' | awk '/nexus-repository_nexus-data$/ {print; exit}')"
test -n "$NEXUS_VOLUME"
sudo docker run --rm \
-v "${NEXUS_VOLUME}:/source:ro" \
-v "${BACKUP_ROOT}:/backup" \
alpine:3.22 \
tar -C /source -czf /backup/nexus-data.tar.gz .
sudo docker compose --env-file .env start nexus
sha256sum "${BACKUP_ROOT}"/* | sudo tee "${BACKUP_ROOT}/SHA256SUMS" >/dev/null
sudo chmod 600 "${BACKUP_ROOT}"/*
echo "Backup complete: ${BACKUP_ROOT}"
启用并手动测试:
sudo chmod 750 /opt/nexus/backup.sh
sudo /opt/nexus/backup.sh
sudo find /srv/backups/nexus -maxdepth 2 -type f -printf '%p %s bytes\n' | tail -n 20
脚本中的卷名通过后缀匹配,首次执行必须人工确认实际卷名。更稳妥的做法是把卷名作为只读配置显式保存。备份完成后还要加密复制到异地;与生产数据同一块磁盘上的 tar 包不算灾备。可以参考 VPS 备份与恢复演练 把目录接入 restic/rclone。
恢复不是“文件能解压”就结束。至少每月在隔离 VPS 或独立 Docker 项目中验证:
- 校验
SHA256SUMS; - 恢复 PostgreSQL dump;
- 恢复
/nexus-data,包含 blob、配置和 node keystore; - 使用相同 Nexus 版本启动;
- 登录 UI,下载一个代理缓存包和一个私有 hosted 包;
- 执行 Maven/npm 客户端测试;
- 记录 RTO、RPO 和失败步骤。
恢复前先停止 Nexus,避免数据库在恢复过程中被写入。数据库与 blob 必须来自同一个备份窗口;时间点不一致会造成元数据指向不存在的 blob,或出现孤立内容。
不要执行 docker compose pull && up -d 就宣布升级完成。固定版本升级建议这样做:
set -euo pipefail
cd /opt/nexus
sudo /opt/nexus/backup.sh
sudo cp .env ".env.before-upgrade-$(date -u +%Y%m%dT%H%M%SZ)"
sudo docker compose --env-file .env pull
sudo docker compose --env-file .env config >/tmp/nexus-upgrade.compose.yaml
然后阅读从当前版本到目标版本跨越的每一个 upgrade path。比如跨越 3.85 需要升级后运行 Repair - Rebuild repository search;跨越 3.87 要检查自定义 Java truststore;跨越 3.91 后默认镜像基础从 UBI 变为 Alpine,依赖容器内工具的脚本可能失效。
修改 .env 中的固定版本后,在维护窗口执行:
cd /opt/nexus
sudo docker compose --env-file .env up -d
sudo docker compose --env-file .env ps
sudo docker compose --env-file .env logs --tail=300 nexus
curl -fsS https://repo.example.com/service/rest/v1/status
升级会改变数据库 schema 时,单纯把镜像 tag 改回旧版不一定安全。可靠回滚是停止新版本,恢复升级前同一时间点的 PostgreSQL 与 /nexus-data,再用原版本启动。务必先在克隆环境演练。
先看日志和数据库:
cd /opt/nexus
sudo docker compose --env-file .env ps
sudo docker compose --env-file .env logs --tail=300 nexus postgres
sudo docker compose --env-file .env exec postgres pg_isready -U nexus -d nexus
常见原因包括 pg_trgm 未安装、数据库用户不是 owner、JDBC schema 错误、首次启动仍在迁移、内存不足或数据卷权限异常。
Named volume 通常由镜像正确初始化。如果改为宿主机目录,必须让 UID 200 可写:
sudo chown -R 200:200 /srv/nexus-data
sudo find /srv/nexus-data -maxdepth 1 -ls
不要粗暴地 chmod -R 777,这会扩大攻击面并掩盖真实 owner 问题。
检查 Caddy 是否传递 Host 和 X-Forwarded-Proto,并确认 HTTP Forwarded Headers capability 启用。参考站内的 Caddy 反向代理与自动 HTTPS 教程 排查 DNS、证书和代理头。
确认:
distributionManagement的<id>与 settings.xml 的<server><id>完全一致;- 发布目标是 hosted,不是 group 或 proxy;
- 账号拥有目标仓库的 add/edit 权限;
- release 版本没有被重复发布策略拒绝;
- 代理没有改写路径或丢失认证头。
确认 npm Bearer Token Realm 已启用,npm 9+ 使用 --auth-type=legacy,registry URL 包含完整 /repository/<name>/ 路径且以 / 结尾。CI token 要与目标 hosted/group 的 URL 精确匹配。
删除组件后,blob 可能只是被标记。先确认清理任务完成,再运行对应 blob store 的 compact 任务。执行前备份,并观察任务日志和 I/O;不要在高峰期对大仓库连续执行重任务。
- 域名 HTTPS 正常,8081/5432 未暴露公网;
- Nexus 使用固定版本,不使用
latest; - Data Store 显示 PostgreSQL,
pg_trgm已安装; - 管理员密码已更换,CI 不使用 admin;
- Maven 下载走
maven-public,发布走 hosted; - npm 下载走
npm-all,发布走npm-hosted; - hosted、proxy、group 的角色权限已经拆分;
- 磁盘、内存、健康、证书和备份失败有告警;
- 数据库、blob、node keystore 在同一窗口备份;
- 已在隔离环境完成一次恢复演练;
- 升级固定 tag 前会检查 upgrade path 并保留可恢复快照。
短期实验可能在 2 vCPU、4 GB RAM 上启动,但这不是稳妥的生产建议。Sonatype 当前小型 profile 基线为 2 vCPU、8 GB RAM 和 20 GB 本地 blob 存储;实际还要为 PostgreSQL、系统缓存、备份和制品增长留余量。小团队建议从 8 GB RAM、100 GB NVMe 起步,并根据命中率、并发和磁盘增长扩容。
Community Edition 可用于个人和小团队的常见制品管理,但版本、格式和功能边界可能变化。选型时应查看当前官方 feature matrix 与许可条款,不要根据旧版 OSS/Pro 博客推断 2026 年能力。需要 HA、企业身份、商业支持或高级治理时,应按官方版本矩阵评估 Pro。
可以。每种格式分别创建 hosted、proxy 和 group,然后让客户端下载走 group、发布走 hosted。这样既能共享上游缓存,也能把内部制品与代理缓存的权限、保留期和备份策略分开。
只管理 OCI/Docker 镜像且重视漏洞扫描、签名、项目复制时,Harbor 更聚焦;需要 Maven、npm、PyPI、NuGet 等多种语言制品时,Nexus 更合适。常见生产架构是两者并存,而不是互相替代。可参考 Harbor 私有镜像仓库教程。
H2 适合低规模或评估,但官方给出了请求量和组件数上限,并明确表示 H2 的容器化部署不受支持。生产环境从第一次启动就使用外部 PostgreSQL,可以避免日后迁移风险,也更符合当前官方部署建议。
不够。使用 PostgreSQL 时,数据库元数据和 blob store 必须协调到同一时间点,还要保留节点身份目录。最强一致性的做法是在维护窗口停止 Nexus,再备份 PostgreSQL 与 /nexus-data,复制到异地,并定期在隔离环境恢复验证。不能恢复的备份只能算“文件副本”。
一套可靠的 Nexus Repository VPS 部署,关键不在于把 8081 页面打开,而在于从一开始选择 PostgreSQL、固定镜像版本、只通过 Caddy 暴露 HTTPS、把 Maven/npm 下载与发布入口分开、限制 CI 权限,并让数据库、blob 与节点身份形成可恢复的同一备份点。
上线后优先完成两件事:第一,观察一周的磁盘增长和缓存命中,设定清理与扩容阈值;第二,在独立环境做一次真实恢复。完成这两步,Nexus 才从“跑在 VPS 上的容器”变成可持续使用的团队制品基础设施。
