Docker Registry 私有仓库

Registry的介绍

Docker Registry 是什么?

Docker Registry(镜像仓库)是一个集中存储、分发 Docker 镜像的服务。你可以把它理解为「镜像的 Git 仓库 + 网盘」:开发者把构建好的镜像推上去(push),其他服务器再从上面拉下来(pull),这样就能保证开发、测试、生产环境用的镜像是完全一致的,避免出现「我这跑得好好的,到你那就报错」的经典玄学问题。

核心组成

  1. 镜像(Image):只读模板,包含运行应用所需的代码、运行时、库、依赖、配置。
  2. 仓库(Repository):一类镜像的集合,通常用名字区分,比如 centos、nginx。
  3. 标签(Tag):镜像的版本号,比如 centos:7.7.1908、nginx:1.27-alpine。
  4. 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 属于轻量级自建,适合中小团队和内网环境。如果场景是多团队、大规模、合规要求高,建议评估以下方案:

方案特点适用规模
HarborCNCF 毕业项目,集成 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

说明

  1. 为什么不写 docker pull registry:不带版本等价于拉 registry:latest,官方更新后标签自动切换;扩容、重装环境时版本不一致,跨大版本存储结构可能损坏。
  2. registry:2.8.3 是 2.x 长期稳定维护分支,适配 Docker 20.10,无已知存储 bug,兼容性最好。
  3. 由于你已经配置了轩辕镜像加速器(xuanyuan.run / xuanyuan.dev),实际拉取会命中国内缓存,速度比直连 Docker Hub 快几倍到几十倍。
  4. 验证拉取完成:
docker images | grep registry

预期输出:

registry       2.8.3           26b2eb03618e   2 years ago     25.4MB

步骤 2:创建持久化存储目录 + 权限修复

# 创建多级存储目录,不存在则自动生成
mkdir -pv /share/dockerimg

# 生产标准权限配置(推荐长期使用)
chown 1000:1000 /share/dockerimg

说明

  1. Registry 容器内部运行进程 UID = 1000,容器内用户不是 root。
  2. 宿主机新建目录默认属主 root,容器进程无写入权限,执行 docker push 会报 permission denied。
  3. chown 1000:1000:把目录归属修改为容器内部运行用户,完美解决权限写入报错。
  4. 临时测试可以用 chmod 777 /share/dockerimg,但生产环境禁止,权限太大存在安全风险。
  5. 验证目录权限:
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

说明

  1. CentOS 7 默认 firewalld 防火墙拦截外部入站端口,本机虽然能 curl 访问,但其他 192.168.2.x 网段服务器会连接超时。
  2. --permanent:写入永久规则,不加此参数仅临时生效,服务器重启后端口拦截恢复。
  3. firewall-cmd --reload:必须执行,否则新增端口规则不生效。
  4. 校验成功标准:输出包含 5000/tcp。

步骤 4:后台启动 Registry 容器(优化重启策略)

docker run -d \
-p 5000:5000 \
--restart=always \
--restart=on-failure:3 \
-v /share/dockerimg:/var/lib/registry \
registry:2.8.3

说明

  1. -d:detach 后台运行,不占用当前终端。
  2. -p 5000:5000:宿主机 5000 端口映射容器内部 5000 端口,外部服务器通过 192.168.2.123:5000 访问仓库。
  3. --restart=on-failure:3:优化原文档的 --restart=always:
  • always:容器无论什么原因停止都会无限重启,磁盘满、端口占用时会死循环。
  • on-failure:3:仅程序异常崩溃时重启,最多重试 3 次,避免资源死循环占用。
  1. -v /share/dockerimg:/var/lib/registry:数据卷挂载
  • 宿主机 /share/dockerimg:永久存储镜像。
  • 容器内 /var/lib/registry:Registry 默认镜像存储路径。
  • 容器删除、重建,宿主机目录镜像文件保留,实现持久化。
  1. 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

结果解读

  1. 全新空仓库,返回 JSON:{"repositories":[]} → 服务完全正常。
  2. 报错连接拒绝:防火墙未放行端口 / 容器未启动。
  3. 证书报错:后续 daemon.json 未配置信任 HTTP 仓库,不影响本机 curl 测试。

预期输出:

{"repositories":[]}

步骤 6:配置 Docker 守护进程信任 HTTP 私有仓库

Docker 默认拒绝无 SSL 加密的 HTTP 仓库,必须在 daemon.json 加入仓库信任列表,同时合并轩辕镜像加速器配置,避免多文件覆盖失效。

6.1 编辑 Docker 配置文件
/etc/docker/daemon.json

设置以下标准 JSON 配置(适配本机 IP + 轩辕双镜像源,无多余逗号,语法合规):

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

配置字段详解

  1. registry-mirrors:轩辕镜像加速器地址,加速拉取 Docker Hub 公共镜像(centos / nginx / redis 等)。
  2. insecure-registries:信任非 HTTPS 仓库地址,告诉 Docker 允许明文 HTTP 方式推拉镜像。这里同时加入了两个轩辕镜像域名(因为它们走 HTTP 协议)和内网 Registry 地址 192.168.2.123:5000。
  3. JSON 强制规范:末尾键值对不能加逗号,否则 Docker 启动失败。
  4. 不要复制原文档里带 @ 符号和空格的写法(如 " @https://xxx/ "),@ 和多余空格会导致 JSON 解析报错,Docker 启动失败。
6.2 配置生效完整流程(两步缺一不可)
# 重载 systemd 系统守护进程,读取新 daemon.json 配置
systemctl daemon-reload

# 重启 Docker 服务,加载新配置
systemctl restart docker
  1. daemon-reload:systemd 缓存 Docker 配置,修改文件后必须重载,否则 Docker 读取旧配置。
  2. restart docker:让 Docker 引擎重新加载信任仓库、加速器配置。
  3. 验证 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,添加相同信任配置:

{
  "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

说明

  1. -B:使用 bcrypt 加密,比默认 MD5 安全得多。
  2. -b:允许在命令行直接传入密码,避免交互式输入(适合脚本场景)。
  3. > /share/auth/htpasswd:把账号密码写入文件,文件会被挂载到容器内供 Registry 读取。
9.2 带认证重启 Registry 容器

先停掉旧容器:

# 查看旧容器 ID
docker ps | grep registry

# 停止旧容器(container-id 替换为实际值)
docker stop <container-id>

# 删除旧容器(数据卷 /share/dockerimg 保留,镜像不丢)
# 可以先不删除,如果出错就删除这个容器
docker rm <container-id>

启动新容器,挂载认证文件:

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

环境变量说明

  1. REGISTRY_AUTH=htpasswd:开启 htpasswd 认证模式。
  2. REGISTRY_AUTH_HTPASSWD_REALM="Registry Login":认证域提示,登录框会显示这个字符串。
  3. 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

安全提醒

  1. 登录凭证默认明文保存在 /root/.docker/config.json,内网环境可接受;公网或敏感环境建议配置 credential helper 加密存储。
  2. 退出登录:docker logout 192.168.2.123:5000。
  3. 忘记密码怎么办:在 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 扫描)
磁盘故障镜像全部丢失见下方备份脚本

每日定时备份脚本
#!/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)
  1. push 镜像报权限不足:执行 chown 1000:1000 /share/dockerimg。
  2. 其他机器访问仓库超时:防火墙放行 5000 端口。
  3. docker pull 报证书不信任:检查 daemon.json IP 是否为 192.168.2.123:5000,重载并重启 Docker。
  4. 容器反复重启:磁盘空间不足、端口 5000 被其他程序占用、目录权限错误。
  5. curl 访问正常但 docker push 失败:daemon.json 配置未生效,重新执行 daemon-reload && restart docker。
  6. Login Succeeded 后仍 no basic auth credentials:重新 docker login,确认 htpasswd 文件中密码正确,重启 registry 容器。

更多推荐