1. 为什么要在内网自建 Docker 镜像仓库
很多团队一开始都是直接用公共镜像仓库拉取基础镜像,本地构建完业务镜像后,要么推到公共仓库,要么干脆只在本地留着。等到服务器规模一上来,问题就全冒出来了:构建机拉取镜像慢、带宽被反复占用、不同环境拿到的镜像版本对不上、CI 流水线里因为网络抖动频繁失败。更麻烦的是,有些内部业务镜像根本不适合放到公网仓库,一旦泄露就是安全事故。
我所在的团队就踩过这个坑。早期十几台机器的时候,大家各自docker pull公共镜像,勉强能忍。后来机器数量涨到几十台,每次发版所有节点同时拉镜像,出口带宽直接被拉满,构建时间从几分钟变成十几分钟。那段时间我几乎天天在排查“为什么这次部署又超时了”。后来痛定思痛,决定在内网搭一个私有镜像仓库,把所有基础镜像和业务镜像都收拢进来,统一管理。
选型上其实没纠结太久。私有仓库方案无非就那么几种:官方 registry、Harbor、Nexus3 这几类。官方 registry 太简陋,没有 Web 界面,权限管理也弱;Harbor 功能全但组件多,部署和维护成本偏高,对中小团队来说有点重。Nexus3 是我比较熟悉的老朋友了,它本来就是个仓库管理器,支持 Maven、npm、PyPI、Docker 等多种格式,一个服务就能把公司里各种包管理需求全兜住。而且它自带 Web 管理界面、用户权限体系、清理策略,用 Docker 部署也就一条命令的事。
所以这篇文章我就围绕docker 搭建 nexus3 并配置 docker 私有仓库这个主题,把整个落地过程讲透。从镜像拉取、容器启动、仓库创建、权限配置,到客户端对接、推送拉取验证、常见坑排查,全部走一遍。适合正在考虑自建镜像仓库的运维、后端、DevOps 同学参考,哪怕你之前没接触过 Nexus,跟着做也能跑通。
2. 部署前的整体设计与关键决策
2.1 为什么用 Docker 跑 Nexus3 而不是裸装
Nexus3 官方提供了多种安装方式:直接下载 tar 包解压、rpm/deb 包安装、Docker 镜像运行。我强烈建议用 Docker 方式,原因有几个。
第一是环境隔离干净。Nexus3 依赖 JVM,裸装的话你得先装 JDK,还得考虑版本兼容。用 Docker 镜像,JVM 和依赖全打包在里面,宿主机只需要有 Docker 就行,不会污染系统环境。
第二是升级和迁移方便。Nexus3 的数据都在一个数据目录里,用 Docker 的话把这个目录挂载到宿主机,升级时直接换镜像重启,数据还在。迁移到另一台机器也就是把数据目录拷过去的事。
第三是资源限制好控制。Nexus3 默认会吃不少内存,用 Docker 可以方便地通过参数限制内存上限,避免它把宿主机内存吃光影响其他服务。
当然也有代价,就是多了一层容器网络,端口映射和存储挂载要配置清楚。但这点成本相比收益完全可以接受。
2.2 数据持久化的规划
这是整个部署里最容易被忽视、也最容易出事的一环。Nexus3 容器里有两个关键目录需要持久化:
/nexus-data:这是 Nexus3 的核心数据目录,包含配置、仓库元数据、blob 存储、用户信息等。这个目录绝对不能丢,丢了等于整个仓库重建。- 日志目录:一般也在
/nexus-data/log下面,跟着一起挂载就行。
我的做法是在宿主机上单独划一个目录,比如/data/nexus3/nexus-data,然后通过-v挂载进容器。这样即使容器被删了,数据还在,重新起一个容器挂上同一个目录就能恢复。
这里有个细节要注意:Nexus3 容器内默认以 UID 200 的用户运行,如果你挂载的宿主机目录权限不对,容器启动会因为写不了数据目录而失败。所以挂载前要先chown -R 200:200 /data/nexus3/nexus-data,把目录属主改成 200。这个坑我见过太多人踩,容器一直重启,日志里报权限拒绝,查半天才发现是目录权限问题。
2.3 端口与访问方式的取舍
Nexus3 默认监听 8081 端口,这是 Web 管理界面和 API 的入口。Docker 私有仓库功能则通常再单独开一个端口,比如 8082,用来处理 Docker 的 push/pull 请求。
为什么 Docker 仓库要单独一个端口?因为 Docker 客户端对 registry 的访问路径有固定约定,比如/v2/、/v2/<name>/manifests/<tag>这些。如果和 Web 界面共用端口,路径容易冲突,配置起来也麻烦。Nexus3 支持为 Docker 仓库单独配置一个 connector 端口,这样职责清晰,Web 管理走 8081,镜像推拉走 8082。
另外还要考虑是否启用 HTTPS。Docker 默认要求 registry 走 HTTPS,如果走 HTTP,需要在客户端的daemon.json里配置insecure-registries。内网环境如果暂时没有证书,可以先走 HTTP 加白名单的方式,但生产环境我还是建议上 HTTPS,后面会讲怎么配。
2.4 内存与 JVM 参数预估
Nexus3 是个 Java 应用,内存给少了会频繁 GC 甚至 OOM。官方建议至少 4GB 内存,我实测下来,如果只是当 Docker 仓库用,2GB 到 4GB 之间比较稳妥。容器启动时可以通过-e INSTALL4J_ADD_VM_PARAMS来调整 JVM 参数,比如设置堆大小。
一个常见的配置是-Xms1g -Xmx2g -XX:MaxDirectMemorySize=2g。这里MaxDirectMemorySize容易被忽略,Nexus3 处理大镜像上传时会用到直接内存,如果这个值太小,上传大镜像可能失败。我一开始没设,传一个 1GB 多的镜像时直接报错,后来加上就好了。
3. 一步步搭建 Nexus3 容器
3.1 拉取镜像与目录准备
先拉官方镜像。这里建议指定一个明确的版本号,不要用latest,避免哪天官方更新了导致行为变化。
docker pull sonatype/nexus3:3.68.0然后准备数据目录:
mkdir -p /data/nexus3/nexus-data chown -R 200:200 /data/nexus3/nexus-data这两条命令看着简单,但第二条是必须的。我见过有人跳过这步,容器起来后一直重启,docker logs里全是Permission denied,折腾半天才反应过来是权限问题。
3.2 启动容器的完整命令
下面是我实际用的启动命令,参数都加了注释说明:
docker run -d \ --name nexus3 \ --restart always \ -p 8081:8081 \ -p 8082:8082 \ -v /data/nexus3/nexus-data:/nexus-data \ -e INSTALL4J_ADD_VM_PARAMS="-Xms1g -Xmx2g -XX:MaxDirectMemorySize=2g" \ sonatype/nexus3:3.68.0逐条解释一下:
--restart always:让容器在宿主机重启后自动拉起,生产环境必备。-p 8081:8081:Web 管理界面端口。-p 8082:8082:预留给 Docker 仓库的 connector 端口,后面在 Nexus 里配置。-v:数据持久化挂载。-e INSTALL4J_ADD_VM_PARAMS:调整 JVM 内存参数。
启动后别急着访问,Nexus3 首次启动要初始化数据,大概需要一到两分钟。可以用docker logs -f nexus3观察,看到类似Started Sonatype Nexus的字样就说明起来了。
3.3 获取初始管理员密码
Nexus3 新版本不再用默认的admin/admin123,而是生成一个随机初始密码,存在数据目录里的一个文件中。进容器查看:
docker exec nexus3 cat /nexus-data/admin.password拿到这串密码后,浏览器访问http://<宿主机IP>:8081,用用户名admin和这串密码登录。登录后会强制要求你修改密码,改成一个自己记得住的强密码。
提示:这个初始密码文件在修改密码后会被删除,所以第一次登录后一定要把新密码记好。如果忘了密码,只能通过删除数据目录里的相关配置重置,代价比较大。
3.4 首次登录后的基础设置
登录进去后,建议先做几件事。一是到设置里把匿名访问关掉,避免任何人都能匿名拉取镜像。二是规划好仓库的命名规范,比如按环境分dev、prod,或者按团队分。三是想清楚清理策略,镜像越堆越多,磁盘迟早会满。
4. 创建 Docker 私有仓库的完整配置
4.1 Docker 仓库的三种类型
Nexus3 里 Docker 仓库分三种类型,理解它们的区别很关键:
| 类型 | 作用 | 典型用途 |
|---|---|---|
| hosted | 本地托管仓库,存放自己推送的镜像 | 存放内部业务镜像 |
| proxy | 代理仓库,缓存远程公共仓库的镜像 | 缓存基础镜像,加速拉取 |
| group | 聚合仓库,把多个仓库合并成一个入口 | 统一拉取入口,简化客户端配置 |
我的建议是三种都建。hosted 放自己的镜像,proxy 代理公共仓库做缓存,group 把前两者聚合起来,客户端只需要配置 group 的地址,拉取时 Nexus 会自动判断是从本地取还是去代理缓存取。这样既省事又高效。
4.2 创建 hosted 类型的 Docker 仓库
进入 Nexus 管理界面,路径是设置 -> Repository -> Repositories -> Create repository,选择docker (hosted)。
关键参数这样填:
- Name:
docker-hosted,名字自己定,但要有辨识度。 - HTTP:勾选,端口填
8082。这就是前面容器映射出来的端口。 - Enable Docker V1 API:一般不用勾,现在都是 V2 API。
- Storage:Blob store 用默认的 default 就行,如果镜像量大可以单独建一个 blob store 方便管理。
- Hosted 下的 Deployment policy:选
Allow redeploy,这样同一个 tag 可以重复推送覆盖。如果选Disable redeploy,同 tag 第二次推送会被拒绝,适合对版本严格管控的场景。
创建完成后,这个仓库就能接收 push 了。
4.3 创建 proxy 类型的 Docker 仓库
同样在创建页面选docker (proxy)。
- Name:
docker-proxy。 - Remote storage:填公共镜像仓库的地址,比如
https://registry-1.docker.io。 - Docker Index:选
Use Docker Hub或者手动指定。 - HTTP:这里注意,proxy 仓库通常不需要单独开端口,它通过 group 或者 hosted 的端口被访问。但如果你想让 proxy 也能独立访问,可以给它分配端口。我一般不给 proxy 单独开端口,统一走 group。
proxy 仓库的作用是缓存。第一次拉某个镜像时,Nexus 会去远程拉取并缓存到本地,第二次再拉就直接从本地返回,速度飞快。这对频繁拉取基础镜像的场景帮助巨大。
4.4 创建 group 类型的 Docker 仓库
选docker (group)。
- Name:
docker-group。 - HTTP:勾选,端口填
8083(或者复用其他端口,但要保证不冲突)。这里我习惯给 group 单独一个端口,客户端统一连这个。 - Member repositories:把
docker-hosted和docker-proxy都加进去,顺序上 hosted 放前面,这样拉取时优先查本地。
创建完这三个仓库,整个 Docker 仓库体系就搭好了。客户端只需要配置 group 的地址,就能同时享受本地镜像和代理缓存。
4.5 配置权限与用户
默认情况下只有 admin 能操作。生产环境肯定不能大家都用 admin,得建专门的用户和角色。
先建角色:设置 -> Security -> Roles -> Create role。比如建一个docker-push角色,赋予nx-repository-view-docker-docker-hosted-add、nx-repository-view-docker-docker-hosted-edit、nx-repository-view-docker-docker-hosted-read这些权限。再建一个docker-pull角色,只给 read 权限。
然后建用户,把角色分配给用户。这样 CI 流水线用一个有 push 权限的账号,普通开发者用只有 pull 权限的账号,权限清晰。
注意:Docker 客户端登录时用的是 Nexus 的用户名密码,但 Docker 对密码里的特殊字符有时处理不好,建议给这类服务账号设置不含特殊符号的强密码,避免登录失败。
5. 客户端对接与推拉验证
5.1 配置 Docker 客户端信任
如果 Nexus 走的是 HTTP,需要在每台客户端的/etc/docker/daemon.json里加上:
{ "insecure-registries": ["192.168.1.100:8083"] }把 IP 和端口换成你自己的。改完重启 Docker:
systemctl restart docker如果走 HTTPS 且有可信证书,就不需要这一步。内网测试阶段用 HTTP 加白名单是最快的,但生产环境强烈建议上 HTTPS。
5.2 登录私有仓库
docker login 192.168.1.100:8083输入前面建的用户名和密码。登录成功后,凭据会存在~/.docker/config.json里。
5.3 给镜像打 tag 并推送
假设本地有个镜像叫myapp:1.0,要推到私有仓库:
docker tag myapp:1.0 192.168.1.100:8083/myapp:1.0 docker push 192.168.1.100:8083/myapp:1.0推送时注意镜像名的格式:<仓库地址>/<镜像名>:<tag>。仓库地址就是 group 的地址。推上去后,在 Nexus 界面里进docker-hosted仓库就能看到这个镜像。
5.4 从私有仓库拉取
在另一台机器上:
docker pull 192.168.1.100:8083/myapp:1.0如果这个镜像在 hosted 里,直接从本地返回;如果不在,Nexus 会去 proxy 对应的远程仓库找,找到后缓存下来再返回。整个过程对客户端透明。
5.5 验证代理缓存是否生效
拉一个公共基础镜像测试:
docker pull 192.168.1.100:8083/library/nginx:latest第一次拉会慢一点,因为要去远程取。拉完后再拉一次,速度应该明显变快,因为已经缓存到本地了。也可以去 Nexus 界面看docker-proxy仓库的存储占用,确认缓存生效。
6. 常见问题与排查技巧实录
6.1 容器启动失败或反复重启
最常见的原因就是数据目录权限不对。排查步骤:
docker logs nexus3如果看到Permission denied或者Cannot write to /nexus-data,就是权限问题。解决方法是停掉容器,chown -R 200:200 /data/nexus3/nexus-data,再启动。
另一个原因是内存不足。如果日志里有 OOM 相关字样,调大-Xmx和MaxDirectMemorySize。
6.2 推送镜像报 413 或超时
大镜像推送失败,通常是两个原因。一是 Nexus 的直接内存不够,前面提到的MaxDirectMemorySize要调大。二是反向代理(如果有)的请求体大小限制,比如 Nginx 默认client_max_body_size是 1MB,传大镜像直接被拦。如果前面挂了 Nginx,要把它调大,比如client_max_body_size 0表示不限制。
6.3 客户端登录报错
docker login失败,先确认地址和端口对不对,再确认insecure-registries配了没有。如果报http: server gave HTTP response to HTTPS client,就是客户端在尝试 HTTPS 但服务端是 HTTP,加白名单即可。
6.4 拉取镜像报 manifest 相关错误
有时候拉取会报manifest unknown或manifest invalid。这通常是镜像 tag 不存在,或者仓库类型配置有问题。确认镜像确实推到了 hosted 仓库,且 group 里包含了这个 hosted 仓库。
6.5 磁盘空间被吃满
镜像越堆越多是必然的。Nexus3 有清理策略功能,可以配置定期删除旧版本镜像。路径是设置 -> Repository -> Cleanup Policies,建一个策略,比如保留最近 30 天或者最近 10 个版本,然后把它应用到 Docker 仓库上。另外可以配置一个定时任务定期执行清理。
下面这张表汇总了常见问题和对应处理方式:
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 容器反复重启 | 数据目录权限不对 | chown 200:200 后重启 |
| 容器 OOM | JVM 内存不足 | 调大 Xmx 和直接内存 |
| 推送 413 | 请求体限制 | 调大代理的 body 限制 |
| 登录失败 | 未配 insecure-registries | 加白名单重启 Docker |
| manifest 错误 | tag 不存在或仓库配置错 | 核对仓库和 tag |
| 磁盘满 | 镜像未清理 | 配置清理策略和定时任务 |
6.6 几个我踩过的坑
第一个坑是没给 group 单独开端口,直接拿 hosted 的端口当统一入口,结果代理缓存用不上,每次拉公共镜像还是走远程。后来给 group 单独配了端口,客户端统一连 group,问题解决。
第二个坑是清理策略配了但没生效,原因是没建对应的定时任务。Nexus 的清理策略需要配合Admin - Tasks里的Cleanup service任务才会定期执行,光建策略不建任务是没用的。
第三个坑是升级镜像时直接docker rm旧容器,结果忘了数据目录挂载的路径,新容器挂了个空目录,数据全没了。好在有备份。所以升级前一定要确认挂载路径,最好先备份数据目录。
7. 生产环境的加固建议
7.1 上 HTTPS
内网也不能完全裸奔。给 Nexus 配 HTTPS 有两种方式:一是直接在 Nexus 里配置 SSL 证书,二是前面挂一个反向代理做 TLS 终止。后者更灵活,证书管理也方便。配好 HTTPS 后,客户端就不需要insecure-registries了,安全性提升一个档次。
7.2 定期备份数据目录
Nexus 的数据目录就是全部家当。建议用定时任务定期打包备份,比如每天凌晨备份一次,保留最近 7 天。备份时最好先停容器或者用 Nexus 自带的备份任务,避免数据不一致。
7.3 监控与告警
至少要监控两件事:磁盘使用率和容器健康状态。磁盘快满时提前告警,避免写满导致服务不可用。容器可以用docker inspect或者外部监控工具检查健康状态。
7.4 高可用考虑
单点 Nexus 挂了整个镜像仓库就不可用。如果团队对可用性要求高,可以考虑多实例加共享存储,或者用对象存储做 blob store 后端。不过对大多数中小团队来说,单实例加定期备份已经够用,没必要过度设计。
我在实际运维这个仓库的过程中最大的体会是,前期把数据持久化和权限这两块做扎实,后面基本不用怎么操心。真正容易出问题的往往是那些看起来不起眼的细节,比如目录权限、内存参数、清理任务,这些地方多花十分钟确认,能省下后面几小时的排查时间。