Docker Registry 私有仓库
Registry的介绍
Docker Registry 是什么?
Docker Registry(镜像仓库)是一个集中存储、分发 Docker 镜像的服务 。你可以把它理解为「镜像的 Git 仓库 + 网盘」:开发者把构建好的镜像推上去(push),其他服务器再从上面拉下来(pull),这样就能保证开发、测试、生产环境用的镜像是完全一致的,避免出现「我这跑得好好的,到你那就报错」的经典玄学问题。
核心组成
- 镜像(Image):只读模板,包含运行应用所需的代码、运行时、库、依赖、配置。
- 仓库(Repository) :一类镜像的集合,通常用名字区分,比如
centos、nginx。 - 标签(Tag) :镜像的版本号,比如
centos:7.7.1908、nginx:1.27-alpine。 - Registry:仓库的集合,托管多个仓库。Docker Hub 是最知名的公共 Registry,Harbor 是企业级私有 Registry,本教程用官方开源的轻量级 Registry 2.8.3。
为什么不能只用 Docker Hub 公共仓库?
| 场景 | 公共仓库问题 | 私有仓库优势 |
|---|---|---|
| 内网环境 | 国内访问 Docker Hub 经常超时、限速 | 内网 5000 端口,带宽稳定可控 |
| 安全合规 | 业务镜像放在第三方平台不合规 | 数据完全自主,物理可控 |
| 定制镜像 | 公司内部基础镜像需要统一管理 | 统一构建、统一版本、统一扫描 |
| 节省带宽 | 多台服务器重复拉取同一镜像 | 一次推送,多次复用,且支持 P2P 分发 |
| 故障隔离 | 公共仓库宕机业务停摆 | 完全自主,不受外部服务影响 |
Registry 在企业中的典型定位
开发者本地构建 → 推送到 Registry → 测试环境拉取 →
自动化测试 → 推送到生产 Registry → 生产服务器拉取部署
Registry 在 CI/CD 流水线里处于承上启下 的核心位置:上游接构建系统(Jenkins / GitLab CI / GitHub Actions),下游接 Kubernetes、Docker Swarm、传统 Docker 主机。它是镜像流转的枢纽。
5 个核心概念
1. 镜像分层与复用
Docker 镜像采用联合文件系统 (UnionFS),由多个只读层叠加组成。docker pull 时会按层下载,已存在的层直接复用,这也是为什么 docker push 速度很快 ------ 只传差异层。
2. 标签(Tag) vs 摘要(Digest)
- Tag :人类可读的版本字符串,比如
v1、latest、1.27-alpine。可变,官方可以重新打 tag 指向不同内容。 - Digest :镜像内容的 SHA256 哈希,比如
sha256:8f2c78ca3141051eef77fb083066222abf20330a2345c970a5a61427aeb2dc7b。不可变,生产环境追求绝对一致性时用 digest 拉取。
3. Registry 镜像存储结构
/share/dockerimg/
└── docker/registry/v2/
├── repositories/ # 仓库元数据(每个镜像一个目录)
│ └── centos/
│ ├── _layers/ # 层引用
│ ├── _manifests/ # 清单(记录 tag 指向哪个 digest)
│ └── _uploads/ # 上传中的临时文件
└── blobs/ # 实际镜像层数据(内容寻址存储)
└── sha256/
理解这个结构,对后面的备份、清理、排障都很重要。
4. 为什么必须配置 insecure-registries?
Docker 客户端默认强制 HTTPS ,因为公网环境明文传输会被中间人攻击篡改镜像。但内网私有仓库为了简化部署通常不开 TLS,这就需要在 daemon.json 里显式声明「这台 Registry 我信得过,允许 HTTP」。
5. --restart 策略怎么选?
| 策略 | 行为 | 适用场景 |
|---|---|---|
no |
容器退出后不重启 | 一次性任务、调试 |
always |
无论什么退出码都无限重启 | 核心服务(但要配合健康检查) |
on-failure:N |
仅非 0 退出码时重启,最多 N 次 | 防止崩溃循环吃资源 |
unless-stopped |
容器被手动停止前一直重启 | 守护进程类服务 |
文档选 on-failure:3 是为了在程序异常时自动恢复 ,又不会因为端口冲突、磁盘满等外部原因导致无限重启死循环。
生产环境的更高阶选择
本文档的 Registry 属于轻量级自建 ,适合中小团队和内网环境。如果场景是多团队、大规模、合规要求高,建议评估以下方案:
| 方案 | 特点 | 适用规模 |
|---|---|---|
| Harbor | CNCF 毕业项目,集成 UI、RBAC、漏洞扫描、镜像复制、Helm Chart 仓库 | 中大型企业首选 |
| Dragonfly | 字节跳动开源的 P2P 镜像分发,十万级并发拉取 | 巨型集群(K8s 节点 1000+) |
| Nexus / Artifactory | 通用制品库管理,支持 Docker、Maven、npm、PyPI 等 | 已有同类基础设施 |
| 云厂商 ACR / ECR / GCR | 全托管,免运维,按量付费 | 上云企业、追求零运维 |
对学习者和小团队来说,从官方 Registry 入手是最扎实的第一步 ------ 会真正理解镜像仓库的底层原理,再去用 Harbor 会事半功倍。
详细步骤
步骤 1:拉取固定稳定版 Registry 镜像
docker pull registry:2.8.3
说明
-
为什么不写
docker pull registry:不带版本等价于拉registry:latest,官方更新后标签自动切换;扩容、重装环境时版本不一致,跨大版本存储结构可能损坏。 -
registry:2.8.3是 2.x 长期稳定维护分支,适配 Docker 20.10,无已知存储 bug,兼容性最好。 -
由于你已经配置了轩辕镜像加速器(
xuanyuan.run/xuanyuan.dev),实际拉取会命中国内缓存,速度比直连 Docker Hub 快几倍到几十倍。 -
验证拉取完成:
docker images | grep registry
预期输出:
registry 2.8.3 26b2eb03618e 2 years ago 25.4MB
步骤 2:创建持久化存储目录 + 权限修复
# 创建多级存储目录,不存在则自动生成
mkdir -pv /share/dockerimg
# 生产标准权限配置(推荐长期使用)
chown 1000:1000 /share/dockerimg
说明
-
Registry 容器内部运行进程 UID = 1000,容器内用户不是 root。
-
宿主机新建目录默认属主 root,容器进程无写入权限,执行
docker push会报permission denied。 -
chown 1000:1000:把目录归属修改为容器内部运行用户,完美解决权限写入报错。 -
临时测试可以用
chmod 777 /share/dockerimg,但生产环境禁止,权限太大存在安全风险。 -
验证目录权限:
ls -ld /share/dockerimg
预期输出:
drwxr-xr-x 2 1000 1000 6 7月 21 10:00 /share/dockerimg
步骤 3:防火墙永久放行 5000 端口
# 永久放行 TCP 5000 端口,重启防火墙/服务器不失效
firewall-cmd --add-port=5000/tcp --permanent
# 重载防火墙规则,配置立即生效
firewall-cmd --reload
# 校验端口是否成功开放
firewall-cmd --list-ports
说明
- CentOS 7 默认 firewalld 防火墙拦截外部入站端口,本机虽然能
curl访问,但其他192.168.2.x网段服务器会连接超时。 --permanent:写入永久规则,不加此参数仅临时生效,服务器重启后端口拦截恢复。firewall-cmd --reload:必须执行,否则新增端口规则不生效。- 校验成功标准:输出包含
5000/tcp。
步骤 4:后台启动 Registry 容器(优化重启策略)
javascript
docker run -d \
-p 5000:5000 \
--restart=always \
--restart=on-failure:3 \
-v /share/dockerimg:/var/lib/registry \
registry:2.8.3
说明
-d:detach 后台运行,不占用当前终端。-p 5000:5000:宿主机 5000 端口映射容器内部 5000 端口,外部服务器通过192.168.2.123:5000访问仓库。--restart=on-failure:3:优化原文档的--restart=always:
always:容器无论什么原因停止都会无限重启,磁盘满、端口占用时会死循环。on-failure:3:仅程序异常崩溃时重启,最多重试 3 次,避免资源死循环占用。
-v /share/dockerimg:/var/lib/registry:数据卷挂载
- 宿主机
/share/dockerimg:永久存储镜像。 - 容器内
/var/lib/registry:Registry 默认镜像存储路径。 - 容器删除、重建,宿主机目录镜像文件保留,实现持久化。
registry:2.8.3:指定固定稳定镜像版本。
验证容器启动成功:
docker ps | grep registry
正常输出会显示容器 ID、镜像、5000 端口映射:
380ff2e59d41 registry:2.8.3 "/entrypoint.sh /etc..." 49 seconds ago Up 48 seconds 0.0.0.0:5000->5000/tcp, :::5000->5000/tcp naughty_williams
步骤 5:API 访问验证仓库服务是否正常运行
curl http://192.168.2.123:5000/v2/_catalog
结果解读
- 全新空仓库,返回 JSON:
{"repositories":[]}→ 服务完全正常。 - 报错连接拒绝:防火墙未放行端口 / 容器未启动。
- 证书报错:后续
daemon.json未配置信任 HTTP 仓库,不影响本机curl测试。
预期输出:
json
{"repositories":[]}
步骤 6:配置 Docker 守护进程信任 HTTP 私有仓库
Docker 默认拒绝无 SSL 加密的 HTTP 仓库,必须在 daemon.json 加入仓库信任列表,同时合并轩辕镜像加速器配置,避免多文件覆盖失效。
6.1 编辑 Docker 配置文件
/etc/docker/daemon.json
设置以下标准 JSON 配置(适配本机 IP + 轩辕双镜像源,无多余逗号,语法合规):
json
cat > /etc/docker/daemon.json <<EOF
{
"registry-mirrors": [
"https://672tq9h17eavbw.xuanyuan.run",
"https://672tq9h17eavbw.xuanyuan.dev"
],
"insecure-registries": [
"672tq9h17eavbw.xuanyuan.run",
"672tq9h17eavbw.xuanyuan.dev",
"192.168.2.123:5000"
]
}
EOF
配置字段详解
registry-mirrors:轩辕镜像加速器地址,加速拉取 Docker Hub 公共镜像(centos / nginx / redis 等)。insecure-registries:信任非 HTTPS 仓库地址,告诉 Docker 允许明文 HTTP 方式推拉镜像。这里同时加入了两个轩辕镜像域名(因为它们走 HTTP 协议)和内网 Registry 地址192.168.2.123:5000。- JSON 强制规范:末尾键值对不能加逗号,否则 Docker 启动失败。
- 不要复制原文档里带
@符号和空格的写法(如" @https://xxx/ "),@和多余空格会导致 JSON 解析报错,Docker 启动失败。
6.2 配置生效完整流程(两步缺一不可)
# 重载 systemd 系统守护进程,读取新 daemon.json 配置
systemctl daemon-reload
# 重启 Docker 服务,加载新配置
systemctl restart docker
daemon-reload:systemd 缓存 Docker 配置,修改文件后必须重载,否则 Docker 读取旧配置。restart docker:让 Docker 引擎重新加载信任仓库、加速器配置。- 验证 Docker 运行状态:
显示 active (running) 代表无报错启动成功。
步骤 7:本地镜像上传至本机 Registry 仓库
7 拉取测试的镜像
docker pull centos:7.7.1908
7.1 镜像打标签(强制格式,不打标无法推送)
docker tag centos:7.7.1908 192.168.2.123:5000/centos:v1
-
192.168.2.123:5000:Registry 仓库地址。 -
centos:镜像名称。 -
v1:业务自定义版本号,可改为v1.0、20260721等。192.168.2.123:5000/centos v1 08d05d1d5859 6 years ago 204MB
centos 7.7.1908 08d05d1d5859 6 years ago 204MB
7.2 推送镜像到私有仓库
docker push 192.168.2.123:5000/centos:v1
查看宿主机持久化存储
ls -lhrt /share/dockerimg/docker/registry/v2/repositories/
成功推送后,目录内会生成 centos 文件夹,代表镜像已持久化保存到宿主机,删除容器镜像不丢失。
步骤 8:局域网其他服务器拉取仓库镜像
编辑 daemon.json,添加相同信任配置:
json
{
"registry-mirrors": [
"https://672tq9h17eavbw.xuanyuan.run",
"https://672tq9h17eavbw.xuanyuan.dev"
],
"insecure-registries": [
"672tq9h17eavbw.xuanyuan.run",
"672tq9h17eavbw.xuanyuan.dev",
"192.168.2.123:5000"
]
}
编辑 daemon.json,添加相同信任配置:
重载并重启 Docker:
systemctl daemon-reload
systemctl restart docker
8.2 目标机器拉取镜像
docker pull 192.168.2.123:5000/centos:v1
常见报错说明
| 报错信息 | 原因 | 解决方法 |
|---|---|---|
x509 certificate / server gave HTTP response to HTTPS client |
未配置 insecure-registries |
在 daemon.json 加入 192.168.2.123:5000,重启 Docker |
connection refused |
仓库主机防火墙未放行 5000 端口 | 在 Registry 服务器执行步骤 3 的 firewall-cmd 命令 |
permission denied |
仓库宿主机存储目录权限未修改 1000:1000 | 在 Registry 服务器执行步骤 2 的 chown 1000:1000 |
步骤 9:配置 Basic Auth 账号密码认证(最后一步)
当前部署完成后,内网任意一台服务器无需账号密码,可随意 push 覆盖、delete 删除业务镜像,无操作审计,故障无法追溯操作人。生产环境必须开启认证。
9.1 安装 htpasswd 工具生成账号密码
yum install -y httpd-tools
# 创建认证目录
mkdir -pv /share/auth
# 创建用户 admin,密码 lengmoxi(-B 强制 bcrypt 加密)
htpasswd -Bbn admin lengmoxi > /share/auth/htpasswd
说明
-B:使用 bcrypt 加密,比默认 MD5 安全得多。-b:允许在命令行直接传入密码,避免交互式输入(适合脚本场景)。> /share/auth/htpasswd:把账号密码写入文件,文件会被挂载到容器内供 Registry 读取。
9.2 带认证重启 Registry 容器
先停掉旧容器:
# 查看旧容器 ID
docker ps | grep registry
# 停止旧容器(container-id 替换为实际值)
docker stop <container-id>
# 删除旧容器(数据卷 /share/dockerimg 保留,镜像不丢)
# 可以先不删除,如果出错就删除这个容器
docker rm <container-id>
启动新容器,挂载认证文件:
javascript
docker run -d \
-p 5000:5000 \
--restart=on-failure:3 \
-v /share/dockerimg:/var/lib/registry \
-v /share/auth:/auth \
-e REGISTRY_AUTH=htpasswd \
-e REGISTRY_AUTH_HTPASSWD_REALM="Registry Login" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
registry:2.8.3
环境变量说明
REGISTRY_AUTH=htpasswd:开启 htpasswd 认证模式。REGISTRY_AUTH_HTPASSWD_REALM="Registry Login":认证域提示,登录框会显示这个字符串。REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd:容器内认证文件路径,对应宿主机/share/auth/htpasswd。
9.3 客户端推拉前必须登录
每台需要使用 Registry 的服务器(192.168.2.123 自身 + 其他 192.168.2.x 机器)都要执行:
docker login 192.168.2.123:5000
# Username: admin
# Password: lengmoxi
登录成功输出:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
安全提醒
- 登录凭证默认明文保存在
/root/.docker/config.json,内网环境可接受;公网或敏感环境建议配置 credential helper 加密存储。 - 退出登录:
docker logout 192.168.2.123:5000。 - 忘记密码怎么办:在 Registry 服务器重新执行
htpasswd -Bbn admin lengmoxi > /share/auth/htpasswd,然后docker restart <container-id>即可。
9.4 验证认证生效
未登录直接 docker pull:
docker pull 192.168.2.123:5000/centos:v1
应该返回:
Error response from daemon: no basic auth credentials
登录后再次拉取即可成功。
生产补充方案速查表
| 缺陷 | 风险 | 解决方案 |
|---|---|---|
| 无账号密码鉴权 | 匿名 push 覆盖、delete 删除 | Basic Auth(步骤 9) |
| 无自动镜像清理 | 磁盘满导致写入失败 | 编写定时清理脚本,删除 _manifests/tags 中无用 tag |
| 无日志审计 | 故障无法追溯操作人 | 开启容器日志持久化 + 对接 ELK |
| 无漏洞扫描 | 不安全镜像流入生产 | 替换 Harbor(自带 Clair 扫描) |
| 磁盘故障 | 镜像全部丢失 | 见下方备份脚本 |
每日定时备份脚本
shellscript
#!/bin/bash
# Registry 镜像存储目录备份脚本
BACKUP_PATH="/data/registry_backup"
SOURCE_PATH="/share/dockerimg"
DATE=$(date +%Y%m%d)
# 创建备份目录
mkdir -p ${BACKUP_PATH}
# 压缩打包镜像数据
tar zcvf ${BACKUP_PATH}/registry_back_${DATE}.tar.gz ${SOURCE_PATH}
# 自动删除 7 天前旧备份,释放磁盘
find ${BACKUP_PATH} -name "registry_back_*.tar.gz" -mtime +7 -delete
加入 crontab 每天凌晨 2 点执行:
0 2 * * * /bin/bash /data/registry_backup.sh
恢复流程 :仓库故障时,停止 registry 容器,解压备份包覆盖 /share/dockerimg,重启容器即可恢复全部镜像。
本机专属排错速查表(适配 192.168.2.123 + Docker 20.10)
push镜像报权限不足:执行chown 1000:1000 /share/dockerimg。- 其他机器访问仓库超时:防火墙放行 5000 端口。
docker pull报证书不信任:检查daemon.jsonIP 是否为192.168.2.123:5000,重载并重启 Docker。- 容器反复重启:磁盘空间不足、端口 5000 被其他程序占用、目录权限错误。
curl访问正常但docker push失败:daemon.json配置未生效,重新执行daemon-reload && restart docker。Login Succeeded后仍no basic auth credentials:重新docker login,确认htpasswd文件中密码正确,重启 registry 容器。