Rocky Linux 10 搭建企业级 Maven 私服:Nexus Repository 3 原生部署、Docker 部署与性能调优

在企业 Java 项目中,如果所有开发机和 CI 节点都直接从 Maven Central 下载依赖,会逐渐暴露出以下问题:

  • 外部仓库访问速度和稳定性不可控;
  • 相同依赖在不同机器上被重复下载;
  • 企业内部组件缺少统一发布、版本管理和权限控制;
  • 无法对外部依赖来源进行集中治理;
  • 构建过程过度依赖公网,难以支持内网、弱网或离线环境;
  • Release、Snapshot 和第三方 JAR 容易混放,制品生命周期混乱。

Maven 私服本质上是企业内部的制品仓库中心。它既可以代理 Maven Central 等远程仓库,也可以托管企业内部构建产物。本文基于 Rocky Linux 10,使用 Sonatype Nexus Repository 3 搭建 Maven 私服,并分别给出原生部署和 Docker Compose 部署方案。

本文编写时的版本基线为 Rocky Linux 10、Nexus Repository 3.94.1。Nexus 版本更新较快,正式部署前应再次查看官方 Release Notes,并将文中的版本变量替换为经过验证的版本。

一、整体架构

建议将 Maven 私服设计为统一入口,而不是让项目分别连接 Maven Central、私有 Release 仓库和 Snapshot 仓库。

flowchart LR
    DEV[开发者 Maven] -->|下载依赖/插件| NGINX[Nginx HTTPS]
    CI[CI/CD 节点] -->|下载与发布制品| NGINX
    NGINX --> NEXUS[Nexus Repository 3]

    NEXUS --> GROUP[maven-public<br/>Group]
    GROUP --> RELEASE[maven-releases<br/>Hosted]
    GROUP --> SNAPSHOT[maven-snapshots<br/>Hosted]
    GROUP --> CENTRAL[maven-central<br/>Proxy]
    CENTRAL --> MC[Maven Central]

    NEXUS --> BLOB[(Blob Store)]
    NEXUS --> DB[(H2 或 PostgreSQL)]

推荐的职责划分如下:

组件 职责
Nginx HTTPS、域名入口、上传大小限制、反向代理
Nexus Repository 仓库管理、权限控制、代理缓存、制品发布
Maven Group 仓库 为开发者和 CI 提供统一下载地址
Hosted 仓库 保存企业自己生产的 Release 和 Snapshot 制品
Proxy 仓库 代理 Maven Central 等外部仓库
Blob Store 保存 JAR、POM、校验文件等二进制内容
H2/PostgreSQL 保存用户、权限、仓库配置、组件元数据等信息

二、部署方案选择

Nexus Repository 当前支持 H2 和外部 PostgreSQL 两种数据库。

2.1 小型环境

适合个人实验室、少量开发者、临时测试:

  • 单节点 Nexus;
  • 内置 H2;
  • 本地 SSD;
  • 每日备份;
  • 2 核 CPU、8 GB 内存作为较稳妥的起点。

2.2 正式生产环境

适合公司研发团队、CI/CD 高频构建或关键基础设施:

  • Nexus 使用独立服务账户运行;
  • 外部 PostgreSQL;
  • Blob Store 与系统盘分离;
  • Nginx HTTPS;
  • 关闭匿名访问;
  • 独立备份与恢复演练;
  • 对 Nexus、磁盘和数据库进行监控;
  • 使用固定版本,不直接追踪 latest

Nexus 的性能通常更容易受到磁盘和网络 I/O 限制,而不是单纯受 CPU 限制。堆内存也不是越大越好,Blob 下载依赖 Linux Page Cache,因此必须给操作系统留下足够内存。

三、环境规划

本文使用以下示例参数:

配置项 示例值
操作系统 Rocky Linux 10 x86_64
主机名 nexus01
Nexus 域名 maven.example.com
Nexus 版本 3.94.1-06
安装目录 /opt/sonatype/nexus-3.94.1-06
软链接 /opt/sonatype/nexus
数据目录 /data/nexus-data
Nexus 内部端口 8081
对外端口 443
运行用户 nexus

检查系统信息:

1
2
3
4
cat /etc/rocky-release
uname -r
uname -m
hostnamectl

设置主机名和时区:

1
2
3
4
5
hostnamectl set-hostname nexus01
timedatectl set-timezone Asia/Shanghai

timedatectl
chronyc tracking

更新系统并安装基础工具:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dnf clean all
dnf makecache
dnf upgrade -y

dnf install -y \
curl \
wget \
tar \
gzip \
unzip \
vim \
jq \
lsof \
bind-utils \
policycoreutils-python-utils \
firewalld \
nginx

四、原生方式安装 Nexus Repository

4.1 创建专用用户

不要使用 root 直接运行 Nexus。创建专用系统用户,并为其保留可用 Shell:

1
2
3
4
5
6
useradd \
--system \
--create-home \
--home-dir /opt/sonatype \
--shell /bin/bash \
nexus

确认用户:

1
2
id nexus
getent passwd nexus

4.2 创建目录

1
2
3
4
5
6
7
8
9
10
mkdir -p /opt/sonatype
mkdir -p /data/nexus-data
mkdir -p /data/nexus-backup

chown -R nexus:nexus /opt/sonatype
chown -R nexus:nexus /data/nexus-data
chown -R nexus:nexus /data/nexus-backup

chmod 750 /data/nexus-data
chmod 750 /data/nexus-backup

生产环境建议为 /data 使用独立 SSD、逻辑卷或云盘,不要让 Blob Store 与 Rocky Linux 根分区争抢空间。

检查文件系统:

1
2
3
4
lsblk -f
df -hT
df -ih
findmnt /data

4.3 下载并校验安装包

Nexus 3.94.1 的 Linux x86_64 安装包已内置所需 Java 运行时,不需要额外安装系统 JDK。

1
2
3
4
5
6
7
8
9
10
11
12
13
export NEXUS_VERSION="3.94.1-06"
export NEXUS_ARCHIVE="nexus-${NEXUS_VERSION}-linux-x86_64.tar.gz"
export NEXUS_DOWNLOAD_URL="https://download.sonatype.com/nexus/3/${NEXUS_ARCHIVE}"

cd /tmp

curl -fL \
"${NEXUS_DOWNLOAD_URL}" \
-o "${NEXUS_ARCHIVE}"

curl -fL \
"${NEXUS_DOWNLOAD_URL}.sha256" \
-o "${NEXUS_ARCHIVE}.sha256"

校验 SHA-256:

1
2
3
4
5
6
7
8
9
EXPECTED_SHA256=$(awk '{print $1}' "${NEXUS_ARCHIVE}.sha256")
ACTUAL_SHA256=$(sha256sum "${NEXUS_ARCHIVE}" | awk '{print $1}')

if [[ "${EXPECTED_SHA256}" != "${ACTUAL_SHA256}" ]]; then
echo "SHA-256 校验失败"
exit 1
fi

echo "SHA-256 校验通过:${ACTUAL_SHA256}"

解压:

1
2
3
4
5
6
7
8
tar -xzf "${NEXUS_ARCHIVE}" -C /opt/sonatype

ln -sfn \
"/opt/sonatype/nexus-${NEXUS_VERSION}" \
/opt/sonatype/nexus

chown -h nexus:nexus /opt/sonatype/nexus
chown -R nexus:nexus "/opt/sonatype/nexus-${NEXUS_VERSION}"

检查目录:

1
2
ls -lah /opt/sonatype
ls -lah /opt/sonatype/nexus

4.4 配置运行用户

Nexus 3.80 及之后的安装包可能不再默认提供 nexus.rc,因此直接创建:

1
2
3
4
5
6
cat > /opt/sonatype/nexus/bin/nexus.rc <<'EOF'
run_as_user="nexus"
EOF

chown nexus:nexus /opt/sonatype/nexus/bin/nexus.rc
chmod 640 /opt/sonatype/nexus/bin/nexus.rc

4.5 修改数据目录

Nexus 的安装目录与数据目录应该分离:

  • 安装目录可以在升级时整体替换;
  • 数据目录必须持久化;
  • Blob、数据库、配置、日志和临时文件均位于数据目录。

先备份 JVM 配置:

1
2
3
cp \
/opt/sonatype/nexus/bin/nexus.vmoptions \
/opt/sonatype/nexus/bin/nexus.vmoptions.bak

修改 /opt/sonatype/nexus/bin/nexus.vmoptions 中的数据路径:

1
2
3
4
-Dkaraf.data=/data/nexus-data
-Dkaraf.log=/data/nexus-data/log
-Djava.io.tmpdir=/data/nexus-data/tmp
-XX:LogFile=/data/nexus-data/log/jvm.log

可以使用命令替换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sed -i \
's#^-Dkaraf.data=.*#-Dkaraf.data=/data/nexus-data#' \
/opt/sonatype/nexus/bin/nexus.vmoptions

sed -i \
's#^-Dkaraf.log=.*#-Dkaraf.log=/data/nexus-data/log#' \
/opt/sonatype/nexus/bin/nexus.vmoptions

sed -i \
's#^-Djava.io.tmpdir=.*#-Djava.io.tmpdir=/data/nexus-data/tmp#' \
/opt/sonatype/nexus/bin/nexus.vmoptions

sed -i \
's#^-XX:LogFile=.*#-XX:LogFile=/data/nexus-data/log/jvm.log#' \
/opt/sonatype/nexus/bin/nexus.vmoptions

创建必要目录:

1
2
mkdir -p /data/nexus-data/{log,tmp,etc}
chown -R nexus:nexus /data/nexus-data

/data/nexus-data/tmp 所在文件系统不能以 noexec 方式挂载,否则 Nexus 可能因为本地动态库无法加载而启动失败。

4.6 JVM 内存调优

Nexus 的主要内存不只有 Java Heap,还包括 Direct Memory、Metaspace、线程栈以及 Linux Page Cache。

可以近似理解为:

1
2
3
4
5
6
Nexus 进程内存
≈ Java Heap
+ Direct Memory
+ Metaspace
+ 线程栈
+ JVM Native Memory

对于一台专用于 Nexus、总内存为 8 GB 的服务器,可以从以下配置开始:

1
2
3
-Xms2g
-Xmx2g
-XX:MaxDirectMemorySize=2g

修改:

1
2
3
4
5
6
7
8
sed -i 's/^-Xms.*/-Xms2g/' \
/opt/sonatype/nexus/bin/nexus.vmoptions

sed -i 's/^-Xmx.*/-Xmx2g/' \
/opt/sonatype/nexus/bin/nexus.vmoptions

sed -i 's/^-XX:MaxDirectMemorySize=.*/-XX:MaxDirectMemorySize=2g/' \
/opt/sonatype/nexus/bin/nexus.vmoptions

建议起点:

主机内存 Heap Direct Memory 适用场景
8 GB 2 GB 2 GB 小型团队、低并发
16 GB 4 GB 4 GB 中型团队、CI 构建较频繁
32 GB 8 GB 8 GB 大型实例,需结合监控验证

注意事项:

  1. -Xms-Xmx 保持一致,避免运行期间反复调整堆;
  2. 不要把 80% 以上物理内存都分给 Heap;
  3. Direct Memory 也会消耗真实内存;
  4. 给 Linux Page Cache、Nginx 和系统服务留出空间;
  5. 调优必须根据 GC、请求延迟、磁盘 I/O 和缓存命中率验证,而不是“内存越大越快”。

4.7 配置 Nexus 应用参数

首次启动前创建配置文件:

1
2
3
4
5
6
7
8
9
10
11
mkdir -p /data/nexus-data/etc

cat > /data/nexus-data/etc/nexus.properties <<'EOF'
application-port=8081
nexus-context-path=/
nexus.security.password.algorithm=PBKDF2WithHmacSHA256
nexus.security.secrets.algorithm=PBKDF2WithHmacSHA256
EOF

chown -R nexus:nexus /data/nexus-data/etc
chmod 640 /data/nexus-data/etc/nexus.properties

建议让 Nexus 只通过 Nginx 对外暴露。若需要进一步限制监听地址,可以通过防火墙仅允许本机或管理网访问 8081。

4.8 配置 systemd

创建 /etc/systemd/system/nexus.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[Unit]
Description=Sonatype Nexus Repository
Documentation=https://help.sonatype.com/
After=network-online.target
Wants=network-online.target

[Service]
Type=forking
User=nexus
Group=nexus

ExecStart=/opt/sonatype/nexus/bin/nexus start
ExecStop=/opt/sonatype/nexus/bin/nexus stop
ExecReload=/opt/sonatype/nexus/bin/nexus restart

Restart=on-failure
RestartSec=10s
TimeoutStartSec=600
TimeoutStopSec=600

LimitNOFILE=65536
LimitNPROC=65535
TasksMax=infinity

NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target

加载并启动:

1
2
systemctl daemon-reload
systemctl enable --now nexus

查看状态:

1
2
3
systemctl status nexus --no-pager -l
journalctl -u nexus -n 200 --no-pager
tail -f /data/nexus-data/log/nexus.log

检查端口:

1
2
ss -lntp | grep 8081
curl -I http://127.0.0.1:8081/

读取初始管理员密码:

1
cat /data/nexus-data/admin.password

浏览器访问:

1
http://服务器IP:8081/

首次登录:

1
2
用户名:admin
密码:/data/nexus-data/admin.password 文件中的内容

登录后立即完成以下操作:

  1. 修改管理员密码;
  2. 配置基础 URL;
  3. 根据实际需要关闭匿名访问;
  4. 创建日常管理账户;
  5. 不要让 CI 使用 admin 账户。

五、配置 Nginx HTTPS 反向代理

5.1 配置 DNS

将域名解析到 Nexus 服务器:

1
maven.example.com -> Nexus 服务器 IP

内网环境也可以使用 dnsmasq、CoreDNS 或企业 DNS。

5.2 创建 Nginx 配置

创建 /etc/nginx/conf.d/nexus.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
upstream nexus_backend {
server 127.0.0.1:8081;
keepalive 32;
}

server {
listen 80;
server_name maven.example.com;

return 301 https://$host$request_uri;
}

server {
listen 443 ssl;
http2 on;
server_name maven.example.com;

ssl_certificate /etc/nginx/ssl/maven.example.com.crt;
ssl_certificate_key /etc/nginx/ssl/maven.example.com.key;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:NEXUS_SSL:20m;
ssl_session_timeout 1d;

client_max_body_size 2g;
client_body_timeout 300s;

location / {
proxy_pass http://nexus_backend;

proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;

proxy_connect_timeout 30s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;

# 大文件上传时避免 Nginx 先完整落盘再转发。
proxy_request_buffering off;
}
}

检查配置:

1
2
3
nginx -t
systemctl enable --now nginx
systemctl reload nginx

5.3 SELinux 配置

不要为了省事直接关闭 SELinux。允许 Nginx 连接后端端口:

1
setsebool -P httpd_can_network_connect 1

若证书或数据目录使用了自定义路径,应检查并设置正确的 SELinux Context,而不是把整个系统改为 Permissive。

5.4 防火墙

启动 firewalld:

1
systemctl enable --now firewalld

仅开放 SSH、HTTP 和 HTTPS:

1
2
3
4
firewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload

确认:

1
firewall-cmd --list-all

不建议直接向公网开放 8081

1
2
firewall-cmd --permanent --remove-port=8081/tcp
firewall-cmd --reload

六、设计 Maven 仓库

一个可维护的 Maven 私服至少应包含以下仓库:

仓库名 类型 用途
maven-releases hosted 企业正式版本
maven-snapshots hosted 企业开发快照版本
maven-central proxy 代理 Maven Central
maven-public group 为开发者提供统一下载入口

6.1 Blob Store 规划

建议至少拆分两类 Blob Store:

1
2
maven-hosted-blob
maven-proxy-blob

这样做的好处:

  • 自研制品与可重新下载的外部缓存物理隔离;
  • Proxy 缓存可按策略清理;
  • 恢复时优先保护 Hosted 制品;
  • 更容易统计真实企业制品容量。

推荐目录:

1
2
/data/nexus-blob/maven-hosted
/data/nexus-blob/maven-proxy

若使用文件型 Blob Store,先创建目录:

1
2
3
4
5
mkdir -p /data/nexus-blob/maven-hosted
mkdir -p /data/nexus-blob/maven-proxy

chown -R nexus:nexus /data/nexus-blob
chmod -R 750 /data/nexus-blob

在 Nexus 管理界面中进入:

1
Settings -> Repository -> Blob Stores -> Create file blob store

6.2 创建 maven-releases

进入:

1
Settings -> Repository -> Repositories -> Create repository

选择:

1
maven2 (hosted)

建议配置:

1
2
3
4
5
6
Name: maven-releases
Version policy: Release
Layout policy: Strict
Content disposition: Inline
Deployment policy: Disable redeploy
Blob store: maven-hosted-blob

Release 一旦发布,通常不应覆盖。相同 GAV 如果允许反复覆盖,会导致构建结果不可复现。

6.3 创建 maven-snapshots

选择:

1
maven2 (hosted)

建议配置:

1
2
3
4
5
Name: maven-snapshots
Version policy: Snapshot
Layout policy: Strict
Deployment policy: Allow redeploy
Blob store: maven-hosted-blob

Snapshot 本身就是开发过程中的可变版本,因此允许重新部署。

6.4 创建 maven-central

选择:

1
maven2 (proxy)

建议配置:

1
2
3
4
5
6
7
Name: maven-central
Remote storage:
https://repo.maven.apache.org/maven2/

Version policy: Release
Layout policy: Strict
Blob store: maven-proxy-blob

可根据网络质量配置:

1
2
3
Maximum component age: 1440 分钟
Maximum metadata age: 1440 分钟
Not found cache TTL: 60~1440 分钟

不要盲目将缓存时间调得极短,否则 Nexus 会频繁请求远端仓库,失去代理缓存价值。

6.5 创建 maven-public

选择:

1
maven2 (group)

建议配置:

1
2
3
4
5
6
Name: maven-public
Blob store: maven-proxy-blob
Member repositories:
1. maven-releases
2. maven-snapshots
3. maven-central

Group 仓库地址:

1
https://maven.example.com/repository/maven-public/

下载统一走 Group,发布则直接写入对应 Hosted 仓库。

flowchart TD
    A[Maven 下载请求] --> G[maven-public]
    G --> R{依次查找}
    R --> HR[maven-releases]
    R --> HS[maven-snapshots]
    R --> P[maven-central]
    P -->|本地未命中| C[Maven Central]
    C -->|缓存制品| P
    P --> G
    G --> A

七、创建 Maven 用户和权限

不要让开发者或 CI 使用 admin

推荐至少创建两个角色:

7.1 只读角色

角色名:

1
nx-maven-reader

需要的典型权限:

1
2
3
4
5
6
nx-repository-view-maven2-maven-public-browse
nx-repository-view-maven2-maven-public-read
nx-repository-view-maven2-maven-releases-browse
nx-repository-view-maven2-maven-releases-read
nx-repository-view-maven2-maven-snapshots-browse
nx-repository-view-maven2-maven-snapshots-read

7.2 发布角色

角色名:

1
nx-maven-deployer

在只读权限基础上增加:

1
2
3
4
nx-repository-view-maven2-maven-releases-add
nx-repository-view-maven2-maven-releases-edit
nx-repository-view-maven2-maven-snapshots-add
nx-repository-view-maven2-maven-snapshots-edit

是否授予 delete 权限应谨慎决定。一般 CI 发布账户不需要删除 Release。

创建用户示例:

1
2
maven-reader
maven-deployer

生产环境建议:

  • 人员账户与机器账户分离;
  • CI 使用单独凭证;
  • 定期轮换密码或 Token;
  • 离职人员立即禁用;
  • 管理员只做管理,不参与日常构建。

八、配置 Maven 客户端

8.1 settings.xml

编辑:

1
~/.m2/settings.xml

完整示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://maven.apache.org/SETTINGS/1.2.0
https://maven.apache.org/xsd/settings-1.2.0.xsd">

<localRepository>${user.home}/.m2/repository</localRepository>

<mirrors>
<mirror>
<id>nexus-maven-public</id>
<name>Company Nexus Maven Group</name>
<url>https://maven.example.com/repository/maven-public/</url>
<mirrorOf>*</mirrorOf>
</mirror>
</mirrors>

<servers>
<server>
<id>nexus-maven-public</id>
<username>maven-reader</username>
<password>${env.NEXUS_READER_PASSWORD}</password>
</server>

<server>
<id>nexus-maven-releases</id>
<username>maven-deployer</username>
<password>${env.NEXUS_DEPLOY_PASSWORD}</password>
</server>

<server>
<id>nexus-maven-snapshots</id>
<username>maven-deployer</username>
<password>${env.NEXUS_DEPLOY_PASSWORD}</password>
</server>
</servers>

<profiles>
<profile>
<id>nexus</id>

<repositories>
<repository>
<id>nexus-maven-public</id>
<url>https://maven.example.com/repository/maven-public/</url>
<releases>
<enabled>true</enabled>
<updatePolicy>daily</updatePolicy>
<checksumPolicy>fail</checksumPolicy>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>warn</checksumPolicy>
</snapshots>
</repository>
</repositories>

<pluginRepositories>
<pluginRepository>
<id>nexus-maven-public</id>
<url>https://maven.example.com/repository/maven-public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>

<activeProfiles>
<activeProfile>nexus</activeProfile>
</activeProfiles>
</settings>

设置环境变量:

1
2
export NEXUS_READER_PASSWORD='替换为只读账户密码'
export NEXUS_DEPLOY_PASSWORD='替换为发布账户密码'

CI/CD 中应使用平台 Secret,不要把密码提交到 Git。

检查生效配置:

1
mvn help:effective-settings

8.2 mirrorOf 的选择

常见配置:

1
<mirrorOf>*</mirrorOf>

含义是所有 Maven 仓库请求都经过私服,适合企业统一治理。

也可以使用:

1
<mirrorOf>*,!special-repository</mirrorOf>

表示除 special-repository 外,其他仓库全部走 Nexus。

不推荐在每个业务项目的 pom.xml 中重复声明 Maven Central 镜像地址。仓库策略应尽可能由企业级 settings.xml 管理。

8.3 Maven 密码加密

生成 Master Password:

1
mvn --encrypt-master-password

将结果写入:

1
~/.m2/settings-security.xml

示例:

1
2
3
<settingsSecurity>
<master>{加密后的Master Password}</master>
</settingsSecurity>

再加密 Nexus 密码:

1
mvn --encrypt-password

把输出写入 settings.xml<password>

Maven 密码加密主要用于避免明文直接暴露,并不能替代严格的文件权限、Secret 管理和凭证轮换。

九、配置项目发布

在项目 pom.xml 中增加:

1
2
3
4
5
6
7
8
9
10
11
12
13
<distributionManagement>
<repository>
<id>nexus-maven-releases</id>
<name>Company Maven Releases</name>
<url>https://maven.example.com/repository/maven-releases/</url>
</repository>

<snapshotRepository>
<id>nexus-maven-snapshots</id>
<name>Company Maven Snapshots</name>
<url>https://maven.example.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>

这里的 <id> 必须与 settings.xml<server><id> 完全一致,否则 Maven 无法找到正确凭证。

9.1 发布 Snapshot

项目版本:

1
<version>1.0.0-SNAPSHOT</version>

执行:

1
mvn clean deploy

制品将被上传到:

1
maven-snapshots

9.2 发布 Release

项目版本:

1
<version>1.0.0</version>

执行:

1
mvn clean deploy

制品将被上传到:

1
maven-releases

9.3 发布第三方 JAR

对于不是由当前 Maven 项目构建出来的 JAR,可使用 deploy-file

1
2
3
4
5
6
7
8
mvn org.apache.maven.plugins:maven-deploy-plugin:3.1.4:deploy-file \
-DgroupId=com.example.thirdparty \
-DartifactId=legacy-sdk \
-Dversion=1.0.0 \
-Dpackaging=jar \
-Dfile=legacy-sdk-1.0.0.jar \
-DrepositoryId=nexus-maven-releases \
-Durl=https://maven.example.com/repository/maven-releases/

如果供应方提供了 POM:

1
2
3
4
5
mvn org.apache.maven.plugins:maven-deploy-plugin:3.1.4:deploy-file \
-Dfile=legacy-sdk-1.0.0.jar \
-DpomFile=legacy-sdk-1.0.0.pom \
-DrepositoryId=nexus-maven-releases \
-Durl=https://maven.example.com/repository/maven-releases/

十、验证 Maven 私服

10.1 清理本地测试依赖

1
rm -rf ~/.m2/repository/org/apache/commons/commons-lang3

重新下载:

1
2
mvn -U dependency:get \
-Dartifact=org.apache.commons:commons-lang3:3.17.0

10.2 查看实际请求地址

1
2
3
mvn -X dependency:get \
-Dartifact=org.apache.commons:commons-lang3:3.17.0 \
2>&1 | grep -E 'Downloading|Downloaded'

输出应指向:

1
https://maven.example.com/repository/maven-public/

10.3 使用 curl 验证

1
2
3
curl -u 'maven-reader:密码' \
-I \
https://maven.example.com/repository/maven-public/

10.4 验证缓存

第一次构建时,Nexus 从 Maven Central 下载依赖并写入 Proxy Blob Store;第二次构建应直接命中本地缓存。

可在 Nexus 中进入:

1
Browse -> maven-central

检查对应组件是否已缓存。

十一、Rocky Linux 10 系统调优

11.1 文件句柄

Nexus 会同时打开大量组件、索引、日志和网络连接,文件句柄过低会导致严重故障。

systemd 中已经配置:

1
LimitNOFILE=65536

同时设置系统级上限:

1
2
3
4
5
6
cat > /etc/sysctl.d/99-nexus.conf <<'EOF'
fs.file-max = 1048576
vm.swappiness = 1
EOF

sysctl --system

检查:

1
2
systemctl show nexus -p LimitNOFILE
cat /proc/$(pgrep -f 'nexus.*java' | head -n 1)/limits

11.2 磁盘与文件系统

推荐:

  • 使用 SSD 或高性能云盘;
  • Blob Store 与系统根分区分离;
  • 避免磁盘使用率长期超过 80%;
  • 监控 inode;
  • 保证临时目录至少有 1 GB 可用空间;
  • 不要在 Nexus 运行期间手动移动或修改 Blob 文件;
  • 不要把正在使用的数据目录当作普通文件目录随意 rsync --delete

检查:

1
2
3
4
df -hT /data
df -ih /data
iostat -xz 1
iotop

如果 await 持续偏高、磁盘利用率长期接近 100%,继续增加 CPU 或线程数通常没有意义,应该先解决存储瓶颈。

11.3 不要盲目调大 Jetty 线程池

更多线程并不等于更高吞吐量。

当磁盘、数据库或上游仓库已经达到瓶颈时,提高线程数只会造成:

  • 更多并发 I/O;
  • 更高的内存占用;
  • 更长的请求排队;
  • 更严重的上下文切换;
  • 故障时形成请求雪崩。

除非已经通过线程转储、请求指标和官方建议确认线程池是瓶颈,否则保持默认配置。

11.4 合理设置 Maven 并行构建

Maven 可以使用并行构建:

1
mvn -T 1C clean verify

但私服整体吞吐取决于:

1
2
3
客户端并发
× CI 节点数量
× 单次构建依赖数量

不要在所有 CI 节点同时无限提高并行度。更合理的方式是:

  1. 先确保 Nexus 本地缓存命中;
  2. 为 CI 节点复用本地 Maven 缓存;
  3. 控制流水线并发;
  4. 再根据 Nexus、PostgreSQL 和磁盘监控逐步提高并行度。

十二、Nexus 仓库调优

12.1 Hosted 与 Proxy 分离

企业自研制品不可替代,Proxy 缓存可以重新下载。两者应使用不同 Blob Store、不同备份优先级和不同清理策略。

12.2 Release 禁止覆盖

Release 仓库建议:

1
Deployment policy: Disable redeploy

这可以避免同一个版本号在不同时间指向不同二进制内容。

12.3 Snapshot 定期清理

为 Snapshot 创建 Cleanup Policy,例如:

1
2
3
4
名称:maven-snapshot-cleanup
格式:maven2
最后下载时间:30 天
最后更新:30 天

再创建定时任务:

1
Cleanup repositories using their associated policies

清理完成后,可在维护窗口执行:

1
Compact blob store

注意:

  • Cleanup 是删除逻辑组件;
  • Compact 才会回收 Blob Store 中可回收的物理空间;
  • 不要在业务高峰期同时运行大量清理、压缩和索引任务。

12.4 Proxy 缓存策略

对于 Maven Central:

  • Release 元数据变化较少,可适当延长缓存;
  • Snapshot 元数据变化快,应使用单独的 Snapshot Proxy 或更短缓存;
  • 404 也会进入 Negative Cache;
  • 若某个刚发布版本持续返回 404,可手动 Invalidate cache,不要第一反应就重启 Nexus。

12.5 Group 成员顺序

推荐顺序:

1
2
3
4
maven-releases
maven-snapshots
maven-central
其他可信 Proxy

企业内部仓库优先,有助于减少不必要的远程访问。

十三、基于 Docker 安装 Nexus

Docker 部署分为两种:

  1. 单容器快速体验;
  2. Nexus + PostgreSQL 的生产化 Compose 方案。

当前 Nexus 官方系统要求明确建议生产环境使用外部 PostgreSQL。对于容器化正式部署,不应把单容器 H2 当作默认生产架构。

十四、Rocky Linux 10 安装 Docker Engine

卸载可能冲突的软件包:

1
2
3
4
5
6
7
8
9
10
dnf remove -y \
docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine \
podman-docker

安装仓库管理工具:

1
dnf install -y dnf-plugins-core

添加 Docker 官方 RHEL 仓库:

1
2
3
dnf config-manager \
--add-repo \
https://download.docker.com/linux/rhel/docker-ce.repo

安装:

1
2
3
4
5
6
dnf install -y \
docker-ce \
docker-ce-cli \
containerd.io \
docker-buildx-plugin \
docker-compose-plugin

启动:

1
systemctl enable --now docker

验证:

1
2
3
docker version
docker compose version
docker info

不要开启未加密、未认证的 0.0.0.0:2375 Docker Remote API。它几乎等价于把主机 root 权限暴露到网络。

十五、Docker 单容器快速体验

创建数据目录:

1
2
3
mkdir -p /data/docker/nexus/nexus-data
chown -R 200:200 /data/docker/nexus/nexus-data
chmod 750 /data/docker/nexus/nexus-data

运行:

1
2
3
4
5
6
7
8
9
10
docker run -d \
--name nexus \
--restart unless-stopped \
--ulimit nofile=65536:65536 \
--memory=6g \
--cpus=4 \
-p 127.0.0.1:8081:8081 \
-e INSTALL4J_ADD_VM_PARAMS="-Xms2g -Xmx2g -XX:MaxDirectMemorySize=2g -Djava.util.prefs.userRoot=/nexus-data/javaprefs" \
-v /data/docker/nexus/nexus-data:/nexus-data \
sonatype/nexus3:3.94.1

查看日志:

1
docker logs -f nexus

读取初始密码:

1
docker exec nexus cat /nexus-data/admin.password

该方案适合:

  • 学习;
  • 功能验证;
  • 短期测试;
  • 小型非关键 HomeLab。

正式环境继续使用下面的 PostgreSQL Compose 方案。

十六、Docker Compose + PostgreSQL 部署

16.1 创建目录

1
2
3
4
5
6
mkdir -p /data/docker/nexus/{nexus-data,postgres-data,initdb}
cd /data/docker/nexus

chown -R 200:200 nexus-data
chmod 750 nexus-data
chmod 700 postgres-data

16.2 创建数据库初始化脚本

创建 /data/docker/nexus/initdb/01-nexus.sql

1
2
3
CREATE SCHEMA IF NOT EXISTS nexus AUTHORIZATION nexus;
GRANT ALL PRIVILEGES ON SCHEMA nexus TO nexus;
CREATE EXTENSION IF NOT EXISTS pg_trgm SCHEMA nexus;

16.3 创建 .env

1
2
3
4
5
6
7
8
cat > /data/docker/nexus/.env <<'EOF'
POSTGRES_DB=nexus
POSTGRES_USER=nexus
POSTGRES_PASSWORD=请替换为高强度数据库密码
NEXUS_DB_PASSWORD=请替换为与POSTGRES_PASSWORD相同的密码
EOF

chmod 600 /data/docker/nexus/.env

.env 不能提交到 Git。

16.4 创建 compose.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
services:
postgres:
image: postgres:17
container_name: nexus-postgres
restart: unless-stopped
environment:
TZ: Asia/Shanghai
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- /data/docker/nexus/postgres-data:/var/lib/postgresql/data
- /data/docker/nexus/initdb:/docker-entrypoint-initdb.d:ro
healthcheck:
test:
- CMD-SHELL
- pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}
interval: 10s
timeout: 5s
retries: 10
shm_size: 256m
networks:
- nexus-net

nexus:
image: sonatype/nexus3:3.94.1
container_name: nexus
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
TZ: Asia/Shanghai

NEXUS_DATASTORE_NEXUS_JDBCURL: >-
jdbc:postgresql://postgres:5432/nexus?currentSchema=nexus&tcpKeepAlive=true&loginTimeout=5&connectTimeout=5&socketTimeout=30
NEXUS_DATASTORE_NEXUS_USERNAME: ${POSTGRES_USER}
NEXUS_DATASTORE_NEXUS_PASSWORD: ${NEXUS_DB_PASSWORD}

INSTALL4J_ADD_VM_PARAMS: >-
-Xms2g
-Xmx2g
-XX:MaxDirectMemorySize=2g
-Djava.util.prefs.userRoot=/nexus-data/javaprefs
-Dnetworkaddress.cache.ttl=5
ports:
- "127.0.0.1:8081:8081"
volumes:
- /data/docker/nexus/nexus-data:/nexus-data
ulimits:
nofile:
soft: 65536
hard: 65536
mem_limit: 6g
cpus: 4.0
networks:
- nexus-net

networks:
nexus-net:
name: nexus-net

YAML 中 JDBC URL 必须整体作为字符串处理,尤其要注意 &。使用折叠块可以降低引号和转义错误概率。

启动:

1
2
3
4
cd /data/docker/nexus
docker compose config
docker compose pull
docker compose up -d

查看:

1
2
3
docker compose ps
docker compose logs -f nexus
docker compose logs -f postgres

读取管理员密码:

1
2
docker compose exec nexus \
cat /nexus-data/admin.password

验证 PostgreSQL 扩展:

1
2
3
docker compose exec postgres \
psql -U nexus -d nexus \
-c '\dx'

查看连接:

1
2
3
docker compose exec postgres \
psql -U nexus -d nexus \
-c 'select usename, application_name, client_addr, state from pg_stat_activity;'

16.5 Docker 方案接入宿主机 Nginx

由于 Compose 将 Nexus 绑定为:

1
127.0.0.1:8081:8081

宿主机 Nginx 可以继续使用前文配置:

1
2
3
4
upstream nexus_backend {
server 127.0.0.1:8081;
keepalive 32;
}

外部用户只能通过 HTTPS 域名访问,不能绕过 Nginx 直接访问 Nexus。

十七、Docker 部署调优

17.1 固定镜像版本

生产环境使用:

1
image: sonatype/nexus3:3.94.1

不要长期使用:

1
image: sonatype/nexus3:latest

更严格的环境可以固定镜像 Digest,避免同名 Tag 被重新推送后得到不同镜像。

17.2 数据目录权限

官方镜像中的 Nexus 进程使用 UID 200,宿主机挂载目录必须可写:

1
chown -R 200:200 /data/docker/nexus/nexus-data

常见错误:

1
2
3
Permission denied
Could not create directory
Unable to write admin.password

优先检查:

1
2
namei -om /data/docker/nexus/nexus-data
ls -ldZ /data/docker/nexus/nexus-data

17.3 SELinux Volume Label

Rocky Linux 默认启用 SELinux。若 bind mount 被拒绝,可为目录设置适当 Label,或者在 Compose 中使用 :Z

1
2
volumes:
- /data/docker/nexus/nexus-data:/nexus-data:Z

如果同一个目录需要被多个容器共享,需根据场景评估 :z。不要因为挂载失败就直接关闭 SELinux。

17.4 容器内存

以下配置:

1
2
-Xmx2g
-XX:MaxDirectMemorySize=2g

并不表示容器最多只用 2 GB,而是至少可能使用:

1
2
3
4
5
2 GB Heap
+ 2 GB Direct Memory
+ Metaspace
+ 线程栈
+ Native Memory

因此给容器设置:

1
mem_limit: 6g

比设置为 3 GB 更合理。

如果容器被 OOM Kill:

1
2
3
4
docker inspect nexus \
--format '{{.State.OOMKilled}}'

journalctl -k | grep -i -E 'oom|killed process'

不要只提高 mem_limit,还应查看:

1
2
docker stats nexus
docker exec nexus jcmd 1 VM.native_memory summary

17.5 PostgreSQL 连接池

Nexus 默认连接池不应随意调大。

连接池过大可能导致:

  • PostgreSQL max_connections 耗尽;
  • 更多上下文切换;
  • 活跃查询相互争抢磁盘;
  • 故障恢复时间变长。

只有通过数据库监控确认连接池成为瓶颈后,才考虑配置:

1
2
environment:
NEXUS_DATASTORE_NEXUS_ADVANCED: "maximumPoolSize=200"

并同步调整 PostgreSQL max_connections,给管理员、监控和维护连接保留余量。

十八、备份与恢复

备份不能只复制 /nexus-data,也不能只执行 pg_dump

一个可恢复的 Nexus 至少包含:

1
2
3
4
5
6
1. 数据库
2. Blob Store
3. Nexus 配置
4. 密钥和证书
5. 版本信息
6. Nginx 配置
flowchart LR
    N[Nexus 实例] --> C[配置与密钥]
    N --> B[Blob Store]
    N --> D[H2/PostgreSQL]
    C --> BK[备份存储]
    B --> BK
    D --> BK
    BK --> DR[恢复演练]

18.1 原生 H2 环境

在 Nexus 管理界面创建任务:

1
Admin - Backup H2 Database

备份目标目录:

1
/data/nexus-backup/database

此外还需要备份:

1
2
3
/data/nexus-data/blobs
/data/nexus-data/etc
/data/nexus-data/keystores

不要在数据库和 Blob Store 不一致的时间点随意拼凑备份。

18.2 Docker + PostgreSQL 冷备份示例

创建备份目录:

1
mkdir -p /data/backup/nexus

停止 Nexus 写入:

1
2
cd /data/docker/nexus
docker compose stop nexus

备份 PostgreSQL:

1
2
3
4
5
6
docker compose exec -T postgres \
pg_dump \
-U nexus \
-d nexus \
-Fc \
> /data/backup/nexus/nexus-db-$(date +%F-%H%M%S).dump

备份 Nexus 数据目录:

1
2
3
4
5
6
tar \
--xattrs \
--acls \
-czf /data/backup/nexus/nexus-data-$(date +%F-%H%M%S).tar.gz \
-C /data/docker/nexus \
nexus-data

恢复服务:

1
docker compose start nexus

同时备份部署文件:

1
2
3
4
5
tar \
-czf /data/backup/nexus/nexus-compose-$(date +%F-%H%M%S).tar.gz \
-C /data/docker \
nexus/compose.yaml \
nexus/initdb

.env 中包含密码,应加密备份并限制权限。

18.3 恢复原则

恢复前必须确认:

  • Nexus 版本与备份版本兼容;
  • 数据库与 Blob Store 来自同一备份窗口;
  • 目录所有者和 UID 正确;
  • 密钥、证书、Secrets 文件完整;
  • 域名和 Base URL 正确;
  • 恢复后执行真实 Maven 下载、Snapshot 发布和 Release 发布验证。

只备份不恢复测试,相当于给故障准备了一份心理安慰剂。

十九、升级 Nexus

19.1 原生部署升级

升级流程:

flowchart LR
    A[阅读 Release Notes] --> B[备份数据库与 Blob]
    B --> C[下载并校验新版本]
    C --> D[停止 Nexus]
    D --> E[解压到新目录]
    E --> F[迁移必要配置]
    F --> G[切换软链接]
    G --> H[启动并验证]
    H --> I{异常?}
    I -- 是 --> J[停止并回滚]
    I -- 否 --> K[完成升级]

使用软链接的价值:

1
/opt/sonatype/nexus -> /opt/sonatype/nexus-3.x.x

升级时只需要切换链接,不必覆盖旧安装目录。

不要直接把旧版本的整个 etcbin 覆盖到新版本。自定义配置应尽量放在数据目录的 override 文件中。

19.2 Docker 升级

修改:

1
image: sonatype/nexus3:新版本

执行:

1
2
3
docker compose pull nexus
docker compose up -d nexus
docker compose logs -f nexus

升级前必须:

  1. 阅读目标版本 Known Issues;
  2. 备份数据库与 Blob;
  3. 检查 Java、数据库和镜像架构要求;
  4. 在测试环境验证;
  5. 保留旧镜像和回滚方案。

二十、常见问题

20.1 Nexus 启动很慢

检查:

1
2
3
4
5
tail -f /data/nexus-data/log/nexus.log
iostat -xz 1
free -h
df -h
df -ih

常见原因:

  • 首次初始化数据库;
  • 磁盘 I/O 慢;
  • 内存不足或频繁 Swap;
  • 数据目录权限错误;
  • /tmp 或 Nexus tmp 挂载了 noexec
  • PostgreSQL 无法连接;
  • DNS 解析慢;
  • 文件句柄不足。

20.2 systemd 显示启动失败

1
2
3
systemctl status nexus -l
journalctl -u nexus -b --no-pager
sudo -u nexus /opt/sonatype/nexus/bin/nexus run

直接以前台方式运行通常能看到更完整错误。

20.3 Docker 容器反复重启

1
2
3
4
docker inspect nexus \
--format '{{json .State}}' | jq

docker logs --tail 300 nexus

重点检查:

  • /nexus-data 是否归 UID 200 所有;
  • JVM 参数是否超出容器内存;
  • PostgreSQL 是否就绪;
  • JDBC URL 是否被 YAML 错误解析;
  • SELinux 是否阻止挂载;
  • 文件句柄是否过低。

20.4 Maven 返回 401

检查三处 ID 是否一致:

1
2
3
settings.xml -> server.id
pom.xml -> distributionManagement.repository.id
deploy-file -> repositoryId

然后检查 Nexus 用户是否具有目标 Hosted 仓库的 addedit 权限。

20.5 Maven 返回 400

常见原因:

  • 把 Release 发布到 Snapshot 仓库;
  • 把 Snapshot 发布到 Release 仓库;
  • 仓库 Version Policy 不匹配;
  • Release 已存在且禁止重复部署;
  • URL 指向 Group 仓库,而不是 Hosted 仓库。

下载可以走 Group,上传必须走 Hosted:

1
2
3
4
5
6
7
8
下载:
/repository/maven-public/

发布 Release:
/repository/maven-releases/

发布 Snapshot:
/repository/maven-snapshots/

20.6 下载到旧 Snapshot

执行:

1
mvn -U clean verify

仍未更新时,检查:

  • Maven 本地缓存;
  • Nexus Snapshot metadata cache;
  • Proxy Negative Cache;
  • 项目版本是否真的带 -SNAPSHOT
  • CI 是否复用了错误的 .m2 缓存。

20.7 Nginx 返回 413

错误:

1
413 Request Entity Too Large

调大:

1
client_max_body_size 2g;

然后:

1
2
nginx -t
systemctl reload nginx

20.8 Nginx 返回 504

检查:

1
2
3
proxy_connect_timeout 30s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;

但不要只调大超时时间,还要检查 Nexus、PostgreSQL 和磁盘是否已经出现性能瓶颈。

二十一、生产环境检查清单

操作系统

  • Rocky Linux 已安装安全更新;
  • Chrony 时间同步正常;
  • SELinux 保持 Enforcing;
  • firewalld 仅开放必要端口;
  • Nexus 使用独立用户运行;
  • 文件句柄上限至少为 65536;
  • 数据盘容量、inode 和 I/O 已监控。

Nexus

  • 使用固定版本;
  • 已修改默认管理员密码;
  • 已关闭不必要的匿名访问;
  • CI 未使用 admin;
  • Release 禁止覆盖;
  • Hosted 和 Proxy Blob Store 分离;
  • Snapshot 和 Proxy 配置了清理策略;
  • 未盲目调大 Jetty 线程池;
  • JVM Heap、Direct Memory 和容器限制匹配。

网络与安全

  • 使用 HTTPS;
  • 8081 未直接暴露到公网;
  • Nginx 上传大小和超时已配置;
  • Docker Remote API 2375 未暴露;
  • 密码未写入 Git;
  • 管理入口仅允许可信网络访问。

备份

  • 数据库已备份;
  • Blob Store 已备份;
  • Nexus 配置、密钥和证书已备份;
  • 备份文件已异地保存;
  • 已执行实际恢复演练;
  • 恢复后验证过 Maven 下载与发布。

二十二、总结

一个真正可用的 Maven 私服,不只是“把 Nexus 跑起来”,而是要同时解决以下问题:

  1. 统一入口:所有 Maven 下载统一经过 maven-public
  2. 制品分层:Release、Snapshot、Proxy 仓库职责明确;
  3. 权限最小化:开发者、CI、管理员使用不同账户和角色;
  4. 存储可治理:Hosted 与 Proxy Blob 分离,配置清理和压缩;
  5. 部署可维护:原生安装使用 systemd 和版本软链接,Docker 使用固定镜像;
  6. 性能可验证:围绕磁盘 I/O、内存、数据库和缓存命中率调优;
  7. 故障可恢复:数据库、Blob、配置和密钥形成一致备份;
  8. 访问可控:Nginx HTTPS、SELinux、firewalld 和内部端口隔离。

对于 HomeLab 或小团队,原生 Nexus + H2 可以快速落地;对于正式生产环境,更推荐 Nexus + PostgreSQL + 独立 Blob Store + Nginx HTTPS。不要把私服当成一个“装完就忘”的工具,它已经是 CI/CD 链路中的关键基础设施:一旦它倒下,构建流水线通常也会跟着集体罢工。

参考资料


Rocky Linux 10 搭建企业级 Maven 私服:Nexus Repository 3 原生部署、Docker 部署与性能调优
https://allendericdalexander.github.io/2026/07/31/devops/linux/rocky/rocky-linux-10-nexus-maven-private-repository/
作者
AtLuoFu
发布于
2026年7月31日
许可协议