
简介面向无法连接外网的服务器环境这份压缩包内置Docker Registry官方镜像的完整离线文件主要用于在内网搭建私有镜像仓库。运维或开发人员不需要在目标机器上执行docker pull直接加载该tar包即可获得可用的registry组件从而解决外部网络受限条件下的镜像存储与分发难题。包体整体仅25.15MB共包含18个文件压缩格式为gz便于U盘拷贝和内部传输。内部以layer.tar承载镜像分层数据json文件记录层配置与manifest清单VERSION标识各层版本信息repositories维护仓库索引结构非常清晰适合直接导入离线主机使用。从内容预览可看到manifest.json与多层layer.tar的组合说明资源打包了符合Docker镜像规范的可加载内容。目前已有436人学习下载这份资源可作为内网基础架构的入门级离线素材帮助用户在隔离网络中快速完成私有仓库的初步搭建与验证。1. registry.tar.gz 是什么镜像包还是仓库数据包决定了你接下来的操作交付现场最常见的场景对方扔给你一个 registry.tar.gz丢下一句“把镜像仓库搭起来”就走了。大部分人下意识把它解压、load 进 Docker然后 run 一个 registry 容器——容器起来了仓库里却是空的。查了一下午才发现这个 tar.gz 打包的是 registry 镜像本身而不是仓库里已推送的数据反过来如果压缩包里是 blobs 和 repositories 目录你又不能拿它直接 load。registry.tar.gz 在容器离线交付里是最常见的仓库交付形态它可能是镜像归档也可能是数据卷快照两种用法完全不同。搞清楚包里装的是哪一样比急着敲 docker load 重要得多。这篇文章要解决的就是判断、部署、生成、迁移以及这些过程里最常踩的坑。2. 先分清两种 registry.tar.gz镜像归档与数据卷快照2.1 判断 tar.gz 内部结构的两种快速手段拿到文件先别急着执行导入命令先用 tar 看一眼顶层结构这一步的成本最低、信息量最大。tar -tzf registry.tar.gz | head -20这个命令只列出归档内容而不展开文件gz 压缩格式会先解压再列目录文件大的时候会慢一些但相比误操作省下来的时间可以忽略。看输出的第一层路径看到manifest.json、layers、repositories这些顶层文件说明这是 docker save 产生的镜像归档看到docker/registry/v2目录说明这是 registry 数据卷的打包结果看到blobs和repositories并列出现则是数据目录快照的另一种常见打包方式。镜像归档保存的是镜像的元数据和分层文件它描述的是 registry 这个程序在某个时刻的完整文件系统状态数据卷快照保存的则是 registry 进程运行时写入的仓库数据也就是你之前用 docker push 推上去的所有业务镜像。一句话区分前者是软件本身后者是软件里的内容。把镜像归档误当成数据包去解压或者把数据包拿去 load都会得到完全不可用的结果。还有一种更直接的判断方法看文件大小。registry 官方镜像解压后通常在两百到三百 MB 左右压缩成 tar.gz 后体积会更小而数据卷快照的大小取决于仓库里已经存放了多少业务镜像几个 GB 甚至几百 GB 都很常见。如果文件明显超过 GB 级别大概率是数据卷快照。2.2 为什么离线部署镜像仓库一定绕不开 registry 镜像单机场景下docker images 里的本地镜像可以直接导出成 tar 包分发一台机器一个 tar 文件靠 docker load 就能恢复。可一旦目标环境是一个集群或者有十几台服务器等着拉取同样的镜像逐台 load 就不现实了。集群里的 kubelet、docker daemon 需要从一个统一地址拉取镜像这个地址就是私有镜像仓库而 registry 就是最轻量的私有仓库实现。选 registry 而不是 Harbor主要看部署环境。Harbor 功能完整带 Web UI、项目权限、复制和漏洞扫描但它依赖外部数据库和 Redis离线交付要同时打包 PostgreSQL、Redis 和 Harbor 三个组件复杂度成倍上升。registry 则只有一个容器没有外部依赖数据落在 /var/lib/registry 目录里挂载出来就是一个数据目录整个仓库的备份、恢复、迁移都是文件操作。对于只需要“内网能拉镜像”的交付场景选 registry 是最可靠的做法。所以离线交付时把 registry 镜像或数据目录压缩成 registry.tar.gz 是行业里最常见的形态。它单文件、易拷贝、能保留 Unix 文件权限拿一个 U 盘就能搬到隔离网络里。理解了这个背景下面所有命令都是在围绕“把软件装起来”和“把数据带过去”这两件事做文章。3. 用 registry.tar.gz 在本地跑通最小离线部署导入、启动与验证3.1 docker load 导入镜像归档先确认导入后的镜像名与版本如果你的 registry.tar.gz 是 docker save 的产物导入命令非常简单。docker load -i registry.tar.gz-i指定输入文件等价于--input。执行成功后 docker 会打印一行类似Loaded image: registry:2的结果注意对比这里显示的镜像名和版本号后面启动容器时要用完全一致的名字。导入后立刻确认镜像是否可见docker images | grep registry这个命令过滤出本地镜像列表里的 registry 记录。如果 grep 输出为空有两种可能一是 tar 包里打包的镜像名不叫 registry二是你拿到的包其实是数据卷快照load 时 docker 已经明确报错。看到报错信息不要慌回到第 2 章的tar -tzf去确认结构。一个容易被忽略的细节docker save 产生的 tar 包文件后缀是 .tar 还是 .tar.gz 不影响 load 结果docker load 能自动识别 gzip 压缩流。所以不要因为文件名带 .gz 就手动先解压一遍直接 load 即可多此一举反而可能破坏 tar 头信息。3.2 启动 registry 容器数据目录挂载、端口映射与 HTTP 仓库配置镜像导入成功后启动一个持久化的 registry 容器。docker run -d \ --name registry \ --restartalways \ -p 5000:5000 \ -v /data/registry:/var/lib/registry \ registry:2每个参数都有明确用途-d让容器后台运行--name registry给容器命名方便后面用 docker logs、docker stop 管理--restartalways保证节点重启后容器自动拉起这在离线环境里尤其重要因为现场大概率没有人在机器重启后手动恢复服务-p 5000:5000把宿主机 5000 端口映射到容器内的 registry 服务端口-v /data/registry:/var/lib/registry把仓库数据持久化到宿主机目录容器删了重建数据不丢。注意/var/lib/registry是 registry 容器内默认的数据目录。如果你拿到的是数据卷快照先把 tar.gz 解压到宿主机 /data/registry 目录下再执行上面的 docker run 命令启动后仓库里就已经有镜像数据了。先解压再启动顺序不能反。接下来处理访问协议。registry 默认跑 HTTP而 Docker 客户端默认只信任 HTTPS 仓库直接执行 docker push 会得到http: server gave HTTP response to HTTPS client的报错。内网环境最常用的做法是在每一台需要访问仓库的机器上配置 insecure-registries。{ insecure-registries: [192.168.1.10:5000] }把这段 JSON 写入 /etc/docker/daemon.json然后执行systemctl restart docker重新加载配置。这一步需要理解insecure-registries 告诉 Docker 客户端这个 IP 的 5000 端口虽然走 HTTP但允许访问。配置里的 IP 要写目标机器的实际内网 IP别写 127.0.0.1——其他机器用 localhost 根本连不到仓库。3.3 验证仓库可用/v2/ 端点、push/pull 回环测试容器起来后先做一次最小验证确认 registry 服务在正常应答。curl -s http://127.0.0.1:5000/v2/返回{}表示服务正常。这个 /v2/ 端点是 registry 的 API 健康检查入口客户端 push 和 pull 之前都会先访问它。随后做一次完整的 push/pull 回环测试确保仓库能写入也能读出这是交付前最关键的一环。docker pull busybox docker tag busybox 127.0.0.1:5000/busybox:test docker push 127.0.0.1:5000/busybox:test docker rmi 127.0.0.1:5000/busybox:test docker pull 127.0.0.1:5000/busybox:test第一行从外网拉取一个最小测试镜像离线环境里可以跳过这步直接用一个已有的业务镜像打 tag。第二行给镜像打上仓库地址前缀的 tag格式是仓库IP:端口/镜像名:标签Docker 客户端判断一个镜像是否属于某个仓库就是看完整镜像名的最前面一段。第三行把镜像推入仓库第四行删掉本地镜像第五行再从仓库拉回来。整个过程走通说明仓库的读写能力没有问题这个 registry 服务才算真正可用。一个常见疑问为什么打完 tag 之后 push仓库里存储的镜像名不包含 IP 和端口registry 存储时自动去掉了主机名部分实际存储路径是按docker/registry/v2/repositories/busybox组织的。理解这个结构下一章的备份与恢复就不会搞错目录。4. 自己生成 registry.tar.gz从拉取、导出到仓库数据迁移4.1 在有外网的机器上拉取 registry:2 并用 docker save 导出镜像归档交付场景里你通常需要在一台有外网的机器上准备好所有交付物。registry 镜像本身要从公共仓库拉取然后导出成 tar.gz 带走。docker pull registry:2 docker save registry:2 | gzip registry.tar.gz这里用管道把 docker save 的输出直接送给 gzip 压缩一步到位生成压缩归档。docker save默认把镜像写入标准输出-o参数可以指定输出文件名但配合管道时不需要。为什么不用docker save -o registry.tar registry:2再单独 gzip两步操作多一次磁盘占用而且压缩前后两个文件都在容易把没压缩的 tar 包误发给现场。管道方式只保留最终的一个 .tar.gz省事也更不容易出错。镜像分层本身已经是压缩过的存储格式所以 gzip 对镜像归档的压缩率有限通常只能再压掉百分之几到十几。这是正常的不要因为压缩率不高就怀疑命令有问题。导出后可以做一个完整性检查避免文件在拷贝过程中损坏。gzip -t registry.tar.gz-t是 test 模式只校验 gzip 流的完整性不解压出原始内容。如果文件损坏gzip 会打印错误信息没有输出就代表结构完整。这个检查不能替代 docker load 验证但它能提前筛掉拷贝损坏的低级问题。4.2 把已有仓库数据打包成 tar.gz数据卷快照与恢复镜像归档适合“还没建仓库、要把软件带进去”的场景如果你的内网已经跑了一个 registry里面存了一堆业务镜像现在要迁移到新机器这时候打包的是数据卷而不是 registry 镜像。tar -czpf registry-backup.tar.gz -C /data/registry .参数拆分-c创建归档-z启用 gzip 压缩-p保留文件权限-f指定输出文件名为 registry-backup.tar.gz-C /data/registry先切换工作目录到数据目录.表示把当前目录下所有内容归档。用-C加.而不是直接tar -czf registry-backup.tar.gz /data/registry是为了让归档内的路径从docker/registry/v2开始而不是从data/registry/docker/registry/v2开始解压时少一层目录嵌套。恢复时先建目录再解压。mkdir -p /data/registry tar -xzpf registry-backup.tar.gz -C /data/registry-x表示解压其余参数与打包时对应。执行后检查 /data/registry 下是否有 docker/registry/v2 目录确认路径层级正确然后按第 3 章的 docker run 命令启动容器即可。数据卷快照能否跨版本恢复一般是可以的registry 2.x 的存储结构向后兼容旧数据挂进新版本 registry 容器通常能直接读取。但如果你最初用的 registry 版本太老比如 2.6 之前的版本建议先在测试环境验证一次 catalogs 能列出来再上生产。4.3 脚本化一条命令完成拉取、导出与压缩每次交付都手工敲上面几条命令总会有一次忘记加-p或者把输出文件名写错。把流程固定成一个脚本能减少很多低级失误。#!/usr/bin/env bash set -euo pipefail REGISTRY_IMAGE${REGISTRY_IMAGE:-registry:2} OUTPUT_FILE${OUTPUT_FILE:-registry-$(date %Y%m%d).tar.gz} docker pull ${REGISTRY_IMAGE} docker save ${REGISTRY_IMAGE} | gzip ${OUTPUT_FILE} gzip -t ${OUTPUT_FILE} docker images | grep ${REGISTRY_IMAGE} echo 打包完成: ${OUTPUT_FILE}set -euo pipefail是四个独立选项的组合-e让脚本在任意命令出错时立即退出-u让未定义变量直接报错而不是静默当作空字符串-o pipefail让管道中任何一段命令失败都导致整个管道返回失败否则在docker save出错时 gzip 还会继续执行最终生成一个残缺的 tar.gz。REGISTRY_IMAGE和OUTPUT_FILE两个变量用${VAR:-默认值}写法允许调用时临时覆盖也可以直接改脚本顶部。执行bash build-registry-bundle.sh就会在当前目录生成带日期的归档文件。最后两行分别校验 gzip 完整性和确认本地镜像存在作为收尾的双重检查。这个脚本只负责生成镜像归档如果你要打包数据卷把中间两行替换成第 4.2 节的 tar 命令即可。5. 避坑registry.tar.gz 部署与迁移中常见的 5 个问题5.1 现象load 成功但 run 时提示镜像不存在或者仓库里空无一物有同事拿着一个 registry.tar.gzload 之后 docker images 里确实出现了 registry 相关的镜像记录但 docker run 时提示Unable to find image registry:2 locally而 docker pull 又因为离线拉不动彻底卡住。还有人 load 后 run 起来了但 curl /v2/_catalog 返回空数组。原因分析第一种情况tar 包里镜像的实际 tag 不是 2可能打成了别的版本标签docker run 命令里写的 tag 与本地镜像 tag 对不上客户端就会尝试重新拉取。第二种情况这个包根本不是镜像归档而是数据卷快照load 进去的要么是乱七八糟的目录文件要么 load 时已经报错被忽略最后启动的是一个全新空仓库。解决办法先执行docker images registry --format {{.Tag}}看本地实际存在哪些 tag再用准确的 tag 启动容器如果 tag 正常但仓库是空的就执行tar -tzf registry.tar.gz | head -20确认包内结构是数据卷就解压挂载不要 load。这个判断做在第一步后面省一整天的排查时间。5.2 现象客户端 push 镜像时返回 405 或 500docker login 也无法通过在交付环境里配好 registry 后业务机器上执行 docker push返回405 Method Not Allowed查看 registry 容器日志发现大量http: server gave HTTP response to HTTPS client报错。如果 push 返回 500常见的是认证配置缺失或仓库目录权限不对。原因分析405 的根源是客户端用 HTTPS 访问了 HTTP 端口握手失败导致请求方式异常不是 registry 本身拒绝写入。500 则大多与仓库存储后端无关而是 htpasswd 认证文件配置了但没有正确挂载进容器或者数据目录的属主不是容器内运行用户。解决办法在所有需要访问仓库的机器上配置 insecure-registries 并重启 docker daemon这是 405 的标准解法。500 则检查容器挂载和环境变量是否一致docker logs registry会给出具体错误行。注意重启 docker daemon 会中断所有正在运行的容器操作前确认窗口期。5.3 现象数据卷解压恢复后/_catalog 列出的是空列表备份机器上打包数据卷时一切正常恢复到新机器、启动容器后curl /v2/_catalog 返回{repositories:[]}仓库里几十个镜像全部消失。原因分析九成是解压路径不对。registry 容器内数据目录固定在 /var/lib/registry但如果你用 docker volume 匿名卷跑过容器数据实际落在 /var/lib/docker/volumes/xxx/_data 下直接挂载新的 -v 目录后读取的就是一个空目录。还有一种情况是打包时没有用-C切换目录解压后数据整体嵌套在多层路径下registry 进程找不到镜像元数据。解决办法解压前先tar -tzf registry-backup.tar.gz | head -5确认归档内第一层路径是docker/registry/v2。如果路径不对解压后用ls /data/registry/docker/registry/v2/repositories检查是否能看到仓库名列表再决定是移动目录还是重新打包。5.4 现象registry 容器启动失败端口占用、目录权限与 SELinuxdocker run 执行后容器秒退docker logs registry看到bind: address already in use或者permission denied。前者是 5000 端口被占用后者在 RHEL/CentOS 系列系统上特别常见SELinux 会阻止容器写入宿主机挂载目录。解决办法端口占用时换一个映射端口比如-p 5001:5000同时所有客户端的 insecure-registries 改成新端口。SELinux 导致的权限拒绝可以给挂载目录打上容器镜像标签chcon -Rt container_file_t /data/registry或者把 SELinux 改为 permissive——如果现场安全要求不严很多人会直接选后者但这里建议优先用 chcon 保留系统安全策略。5.5 现象磁盘占用比预期大一倍blobs 与镜像分层重复存储仓库跑了一个月/data/registry 目录占用空间远超所有镜像体积之和。这是因为每次重新推送相同镜像即使内容完全一样registry 也会按新上传的 blob 写入存储如果客户端侧没有使用正确的镜像缓存同一层会被重复存储多次。解决办法启用 registry 的垃圾回收。手动执行docker exec registry bin/registry garbage-collect /etc/docker/registry/config.yml可以清理未被引用的 blob但注意 GC 执行期间不要有并发推送否则刚写入的 blob 可能被误删。想要从根源上降低重复占用客户端侧尽量复用本地已缓存的分层少用 tar 包直接 load 再 push——离线环境里这一点约束较难建议定期对数据目录做一次 GC 和备份。6. 进阶给离线 registry 加认证与删除开关并做一次完整迁移演练交付到现场后通常还需要两道加固访问认证和删除接口开关。先用 htpasswd 生成认证文件。docker run --entrypoint htpasswd registry:2 -Bbn admin your-password /data/htpasswd这条命令借用 registry:2 镜像自带的 htpasswd 程序生成密码哈希-B指定 bcrypt 算法-b从命令行读取密码-n输出到标准输出而不是写入文件admin是用户名。不要用手工拼装的明文密码文件registry 的 htpasswd 认证只认哈希格式。然后带认证参数重启容器。docker run -d \ --name registry \ --restartalways \ -p 5000:5000 \ -v /data/registry:/var/lib/registry \ -v /data/htpasswd:/auth/htpasswd:ro \ -e REGISTRY_AUTHhtpasswd \ -e REGISTRY_AUTH_HTPASSWD_REALMRegistry Realm \ -e REGISTRY_AUTH_HTPASSWD_PATH/auth/htpasswd \ -e REGISTRY_STORAGE_DELETE_ENABLEDtrue \ registry:2REGISTRY_AUTHhtpasswd打开认证后面两个环境变量指定认证文件路径和 Realm 名称REGISTRY_STORAGE_DELETE_ENABLEDtrue开启删除接口配合 docker registry API 可以按 tag 删除镜像。注意开启认证后所有客户端需要先docker login 仓库IP:5000才能 push。迁移演练是最容易被跳过但价值最高的环节。我的习惯是任何 registry.tar.gz 交付物到现场后第一件事是拿一台临时机器做恢复演练包括检查包结构、load 或解压、启动容器、push 一个测试镜像再 pull 回来。演练通过再上真实业务节点演练不通过当场定位问题而不是让客户陪你踩坑。最开始做离线交付时我拿到 registry.tar.gz 都是直接 load直到一次现场 push 405 排查了三个小时才发现包里拆开是个数据卷目录。那之后我养成了固定习惯接手任何带 registry 字样的压缩包第一件事永远是tar -tzf看路径结构第二件事才是决定 load 还是解压。这个顺序帮我避开了绝大部分低级事故希望也能帮到你。本文还有配套的精品资源点击获取