简介:这是一份面向无法连接外网环境的Docker Registry镜像资源包,适用于企业内网或隔离网络快速搭建私有镜像仓库,无需访问外部镜像仓库即可在受限网络中自助完成镜像分发与版本管理,满足本地镜像离线部署需求。包内是基于官方Registry生成的完整镜像数据,共18个文件,其中7个json文件记录镜像构建配置与层间关联,5个layer.tar保存实际镜像层数据,5个VERSION标识各层版本,另有repositories维护仓库与镜像标签的映射关系;压缩包整体约25.15MB,结构清晰,可直接用于内网镜像导入与仓库初始化。资源下载量已达436人,对刚接触Docker私有仓库搭建的运维人员来说,是一份轻量且实用的离线素材。通过该包可省去外网拉取镜像的步骤,配合内网环境即可完成基础Registry服务部署,同时也可作为理解Docker镜像分层结构及元数据组织的参考样例。
1. 拿到 registry.tar.gz 只是第一步:离线镜像仓库的完整落地链路
做内网交付时,最常碰到的就是「服务端连不上外网,但又要部署一套带镜像的私有仓库」。这个时候registry.tar.gz就是一个已经被压缩好的 Docker Registry 离线镜像包。很多人以为把它拷过去、docker load一下就算完事,结果启动容器之后一推镜像就被http: server gave HTTP response to HTTPS client卡住,或者容器重启后镜像全部消失。问题不在包本身,而在对 Registry 镜像的启动参数和客户端侧的配置缺乏理解。
这份资源解决的核心问题是:在没有公网的环境里,用registry.tar.gz快速搭建一个可用的私有镜像仓库,并且让docker push、docker pull走的通。适合在内网部署、离线交付、灾备迁移场景下手动操作 Docker Registry 的工程师——尤其是那些对docker save/docker load只停留在命令层面,没有真正处理过镜像层结构和仓库配置的人。
2. 从拉取到压缩:把 registry:2 做成 tar.gz 的标准链路
2.1 为什么选 registry:2 而不是 docker/distribution
先把镜像选择这件事说清楚。当前 Docker Registry 的官方镜像标签是registry:2,对应的上游项目就是docker/distribution(最新版本已经整合到distribution/distribution)。你会发现 Docker Hub 上还有一个叫docker/distribution的镜像,但从 2.8 版本之后,官方推荐的就是registry:2,原因在于它基于alpine基础镜像构建,体积更小,而且修复了多个存储驱动相关的安全漏洞。
我在离线交付时通常只用registry:2这个 tag,而不是registry:latest。原因很简单:latest会随上游更新变化,离线环境下你无法控制docker pull的行为,一旦某天镜像内容发生不兼容变更,你在目标机上复现的仓库行为就和本地不一致。固定 tag 本质上是给整个交付链路加了一把锁。
另外有人说可以用docker export把容器打成 tar,再导入成镜像。这是典型的概念混淆。docker save保存的是镜像层,包含完整的元数据和层信息,导入后可以继续作为镜像来run;docker export导出的是容器文件系统快照,丢掉了镜像的历史层和入口配置,导入后不能再保留原有镜像结构,不适合作为 registry 镜像的交付格式。
2.2 save → gzip → 传输:每一步的命令和参数含义
先在联网机器上把镜像拉下来,然后打包压缩。整个链路如下:
docker pull registry:2 docker save registry:2 -o registry.tar gzip -k registry.tar这里说明一下三个命令各自的职责。第一条docker pull把镜像从 Docker Hub 拉到本地镜像仓库,如果本地已经存在同名的旧版本,可以加--platform linux/amd64强制拉取指定平台架构的镜像,避免在多架构环境下拿到错误的arm64版本。第二条docker save的参数-o指定输出文件路径,默认会保存为未压缩的 tar 格式。第三条gzip -k中的-k保留原始文件,只生成registry.tar.gz。
我在实际交付时不会直接对 tar 做流式压缩,而是先落盘,再压缩,因为流式操作出错时排查难度更大。比如以下命令虽然一行搞定:
docker save registry:2 | gzip > registry.tar.gz但如果传输中断,你无法判断到底是 save 阶段还是 gzip 阶段出了问题。分步执行的好处是每一步的产物都可以独立校验,比如gzip -t registry.tar.gz验证压缩包完整性,docker load -i registry.tar验证镜像 tar 的可用性。这对离线交付来说非常重要,因为你大概率不会在目标环境重来一遍。
2.3 在目标机上 load:镜像层与 tag 的恢复逻辑
目标机上执行:
docker load -i registry.tar.gz等一下,很多人在这里会翻车。docker load并不支持直接读取 gzip 压缩文件,它期望的输入是未压缩的 tar 格式。某些高版本 Docker 会自动探测并解压,但这不是所有版本都支持的行为。稳妥的做法是先解压再导入:
gunzip registry.tar.gz docker load -i registry.tar导入完成后,用docker images | grep registry确认镜像是否存在。这里有第二个翻车点:docker load之后,镜像的完整名称是registry:2还是library/registry:2,取决于保存时镜像的原始 tag。如果docker save时用的命令是docker save registry:2,那么映像的 Repository 就是registry,Tag 是2。如果你看到的是docker.io/library/registry:2,不用紧张,那只是 Docker 默认的命名空间前缀,不影响docker run registry:2的执行。
3. 目标机容器化部署:从 docker run 到 Registry 服务对外可用
3.1 目录规划与容器启动参数
镜像导入只是开始。要让 Registry 真正服务外部客户端,必须正确配置容器参数。我习惯在部署前先建立一个宿主机的数据目录,比如/data/registry,用来存放仓库的镜像数据。
mkdir -p /data/registry docker run -d \ --name registry \ --restart=always \ -p 5000:5000 \ -v /data/registry:/var/lib/registry \ -e REGISTRY_STORAGE_DELETE_ENABLED=true \ registry:2逐项说明这些参数的含义。-d后台运行容器;--restart=always保证机器重启后 Registry 容器能自动拉起,这是离线环境下最省心的配置——不用写 systemd 服务来守护容器;-p 5000:5000把宿主机 5000 端口映射到容器内部的 5000 端口,Registry 默认监听 5000;-v /data/registry:/var/lib/registry是关键,/var/lib/registry是 Registry 容器内部存储镜像的默认路径,如果不挂载宿主目录,容器删除后所有镜像会随之消失;REGISTRY_STORAGE_DELETE_ENABLED这个环境变量用于开启镜像删除能力,如果预见到以后需要清理不用的仓库,建议一开始就打开,否则外层 API 会返回 405。
3.2 自检清单:验证容器是否真的提供服务
容器启动后,不要急着跳到客户端配置。先做一组自检,确认容器内部的服务链路是通的。
docker ps | grep registry curl -v http://127.0.0.1:5000/v2/ docker logs --tail 50 registry第一条命令确认容器处于Up状态。第二条命令访问 Registry 的 v2 API 根路径,如果返回200 OK且响应体为空,说明服务正常;如果连接被拒绝,先看映射端口是否被占用,再检查容器日志。第三条命令是排障的核心手段——docker logs会输出 Registry 的访问日志和错误信息,比如http: server gave HTTP response to HTTPS client这类问题在服务端日志里也会留下痕迹。
这里要额外说明外网场景的端口问题。如果你的宿主机在云环境或者公司防火墙之后,127.0.0.1通不代表外网 IP 通。你需要检查云平台安全组、iptables 和 firewalld 对 5000 端口的放行策略。常见做法是:
firewall-cmd --permanent --add-port=5000/tcp firewall-cmd --reload但注意,在裸机部署时,很多生产环境是没有 firewalld 的,你需要确认你的发行版和初始化工具链。我见过有人在 CentOS 7 上配了 firewalld 规则依然不通,最后发现是云平台安全组没放行;也有人反过来,安全组放行了但本机 firewalld 没开,导致局域网其他机器访问失败。结合ss -lntp | grep 5000和curl http://<宿主机IP>:5000/v2/两条命令一起看,能快速缩小问题范围。
4. 客户端对接与私有仓库配置:push/pull 联调全流程
4.1 insecure-registries 的真正含义
Registry 容器本身已经跑起来了,但客户端docker push到私有仓库还会默认拒绝。原因是 Docker 客户端对所有非 HTTPS 的私有仓库地址默认不信任,除非你在配置里显式声明。
编辑/etc/docker/daemon.json:
{ "insecure-registries": ["192.168.1.100:5000"] }这里的关键点是数组中的地址必须包含端口号,或者写为 CIDR 形式如["192.168.1.0/24"]。配完之后需要重启 Docker 守护进程:
systemctl restart docker注意,daemon.json配置的是 Docker 客户端所在机器的配置,而不是 Registry 服务器所在机器的配置。如果其他开发机也要向这个仓库推送镜像,每台机器都要各自配置。这是很多初学者的认知盲区,他们会只在一台机器上改配置,然后换一台机器推送时又提示 HTTPS 错误。
为什么我用insecure-registries而不是给这一台机器配自签证书?内网环境配 CA 证书的流程复杂,需要为每台客户端分发证书文件,而且证书过期是一个持续的运维负担。对一个内部使用的仓库来说,insecure-registries是对管理与便捷性妥协后的现实选择。如果你想在生产环境做到全链路加密,就去用certbot或自建 CA 签证书,然后配置REGISTRY_HTTP_TLS_CERTIFICATE和REGISTRY_HTTP_TLS_KEY环境变量。
4.2 从 docker tag 到 push:一条完整的验证链路
配置完成后,随便拿一个本地镜像测试。
docker pull hello-world docker tag hello-world 192.168.1.100:5000/hello-world:v1 docker push 192.168.1.100:5000/hello-world:v1执行docker tag的目的是让镜像名带上仓库地址前缀,Docker 会将仓库地址/镜像名:tag解析为一次推送目标。有人会问为什么不能直接docker push 192.168.1.100:5000/hello-world,这个问题的答案是 Docker Hub 默认仓库不允许你自定义地址前缀——实际上少打 tag 这条命令,push 的结果就是把镜像推到了 Docker Hub,而不是你的私有仓库。
推送完成后,在 Registry 服务器上验证仓库里的数据:
curl http://127.0.0.1:5000/v2/_catalog curl http://127.0.0.1:5000/v2/hello-world/tags/list第一条命令返回仓库内所有镜像的列表,{"repositories":["hello-world"]}就是成功了。第二条命令返回这个镜像下所有的 tag。这两条 API 是排查问题时最常用的,也很适合写进你的发布脚本里做冒烟验证。
5. 避坑笔记:离线交付 registry.tar.gz 最常见的五个翻车现场
5.1 docker load 毫无输出,镜像也不出现
现象:执行docker load -i registry.tar后,命令很快结束,但docker images里找不到registry这个镜像。
原因:tar 文件中并没有包含镜像数据,或者被负载时的镜像名不是预期值。一种常见场景是打包时使用了docker export而非docker save,导致 tar 内部没有镜像层的 MANIFEST 文件;另一种是从别人那里拷贝的 tar 包,实际内容已经是镜像 ID,而不是仓库名加 tag 的映射。
解决:先tar -tf registry.tar查看文件内部结构,确认是否存在manifest.json和repositories文件。如果没有,说明打包方式有问题,需要重新用docker save。如果结构正常但 tag 不对,用docker images --no-trunc查看镜像 ID,再手动docker tag。
5.2 curl 127.0.0.1 通,局域网不通
现象:在 Registry 宿主机上执行curl http://127.0.0.1:5000/v2/正常,但另一台机器访问http://192.168.1.100:5000/v2/连接超时。
原因:宿主机上的服务正常监听,但流量在防火墙或者安全组被拦截。云环境最常见,裸机环境则需要检查 iptables。
解决:按从物理到逻辑的顺序排查——先 ping 宿主机确认网络通,再用ssh登录宿主机执行ss -lntp | grep 5000确认监听地址是否为0.0.0.0:5000或[::]:5000,最后检查iptables -L INPUT -n或云平台安全组。值得注意的是,docker默认会往iptables的DOCKER链里加规则,如果手动改过默认策略,可能影响到端口转发。
5.3 push 时提示 http: server gave HTTP response to HTTPS client
现象:客户端推送镜像,报错http: server gave HTTP response to HTTPS client。
原因:仓库地址没有配置到insecure-registries,或者配置后没有重启 Docker 守护进程。Docker 客户端对非 HTTPS 的私有地址默认采取拒绝策略。
解决:在客户端/etc/docker/daemon.json的insecure-registries数组中补上仓库地址,重启 Docker。这里有一个细节:不要重启 Registry 容器,只重启客户端守护进程就够了。很多人会习惯性重启仓库,浪费时间且无意义。
5.4 registry 容器删掉后镜像没了
现象:容器因为某种原因被docker rm或机器重启后,curl /v2/_catalog返回空列表,仓库数据丢失。
原因:启动容器时没有挂载/var/lib/registry数据卷,或者挂载了但目录不对。本地镜像数据实际存放在容器可写层,容器消失就没了。
解决:确认启动命令中含有-v /data/registry:/var/lib/registry。如果之前启动时忘了挂载,最稳妥的办法是停止容器后删除,用带-v的命令重新docker run,然后重新推送一遍镜像。手工从容器里docker cp目录数据也能补救,但效率极低,不推荐在生产环境用。
5.5 gzip 压缩后大小没怎么降
现象:ls -lh registry.tar显示 120MB,压缩成registry.tar.gz后还是 100MB 左右,几乎没变化。
原因:镜像内部的文件已经不是文本型内容,镜像层本身就可能是压缩过的数据;而且 registry:2 基于 Alpine,基础镜像本来就小,堆叠的层很少,压缩空间有限。
解决:不要纠结压缩率。gzip在这里的目的是降低网络传输时间,不是磁盘占用。如果内网带宽足够,直接用未压缩的registry.tar也完全可以;但为了吻合交付规范,保持 gzip 是很多公司的标准,传输完毕后在目标端解压即可。
6. 进阶用法:给 registry.tar.gz 做一次完整的校验与版本管理
离线交付最怕的其实是「带错版本」。我见过不止一次,生产环境部署完仓库后,发现镜像包拷的是旧版本的 Registry,导致客户端兼容性问题。从那以后,我每次打包镜像都不直接docker save,而是先做一个带固定版本号的交付目录,并把校验和写入文件一起交付。
mkdir -p image_delivery/registry_2.8.3 docker pull registry:2.8.3 docker save registry:2.8.3 -o image_delivery/registry_2.8.3/registry_2.8.3.tar gzip -k image_delivery/registry_2.8.3/registry_2.8.3.tar cd image_delivery/registry_2.8.3 sha256sum registry_2.8.3.tar.gz > SHA256SUMS cat SHA256SUMS这个流程里有三个细节。一是固定镜像的完整版本号registry:2.8.3,确保本次交付的包与测试环境完全一致;二是生成SHA256SUMS文件,目标机导入前先执行sha256sum -c SHA256SUMS,能拦截传输过程中静默损坏的文件;三是把目录名与镜像版本绑定,而不是只写registry.tar.gz,这样后续排查时能直接定位到具体包版本。
目标机的校验动作对应是:
cd image_delivery/registry_2.8.3 sha256sum -c SHA256SUMS gunzip registry_2.8.3.tar.gz docker load -i registry_2.8.3.tarsha256sum -c会读取SHA256SUMS文件,自动比对当前目录下的.tar.gz文件哈希是否匹配。匹配时输出OK,不匹配时输出类似FAILED的信息并在结束时返回非零退出码。这个习惯特别适合多文件交付场景。如果已经忘了原始包的内容,可以重新用docker images --digests查看当前镜像的 digest,再和仓库中的数据做比对——前提是你在源机上保留了镜像。
另外建议每交付一次,就在仓库侧用curl http://127.0.0.1:5000/v2/_catalog记录一份仓库清单,和 tar 包一起归档。这样半年后有人问你「这个仓库里当时推了哪些镜像」,不用再翻运维记录,打开归档文件就能回答,比docker ps历史记录更清晰。从那以后我每次离线部署 Registry 都会强制走一遍 save → 校验 → load → 验证/_catalog的完整闭环,这四步缺一步,我就觉得这包还没交付到位。希望帮到你。
本文还有配套的精品资源,点击获取