news 2026/9/16 21:32:51

Docker镜像仓库选型与部署:从Docker Hub到Harbor完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Docker镜像仓库选型与部署:从Docker Hub到Harbor完整指南

要说 Docker 用得久了,一定会遇到一个问题:镜像从哪来、推到哪去。如果只是本地开发,docker pull一下官方镜像还算舒服;可一旦你开始建设环境、对接测试、部署上线,没有自己的 docker 仓库,整套流程就会像没有仓库的物流公司,全靠手工搬运。这个主题我压了很久没写,因为“仓库”两个字看似简单,实际拆开之后涉及的东西不少:公有的 Docker Hub、轻量的 Registry、企业级的 Harbor,三者的定位、用法、坑都完全不一样。这篇文章就把这三条路线串起来讲清楚,从原理到落地的完整过程都覆盖,适合已经会docker rundocker ps、但还没认真研究过“镜像分发”的读者。

1. 为什么仓库是容器实践绕不开的一环

1.1 从镜像分层说起,仓库到底存了什么

很多初学者会有一个误区,觉得 Docker 镜像就是“一个文件”,仓库就是“放文件的网盘”。实际上镜像背后是一套 OCI 规范的东西:一个镜像由多个 layer 组成,每个 layer 在仓库里是以 blob 存的,另外还有 manifest 记录这些 layer 的清单和配置,更上层还有 tag 来引用不同的 manifest。

所以你在本地敲docker pull nginx:1.23的时候,Docker 客户端不是直接下“一个 nginx 文件”,而是先去拿 manifest,再根据 manifest 去上面列出的每个 layer 地址逐个下载。这也就是为什么仓库不是简简单单的 FTP——它必须有一套完整的 API 来回答问题:“这个 tag 对应的 manifest 是什么”“我该去哪层下文件”“这个 blob 存在吗”。所有 Docker 仓库,不管是 Hub、Registry 还是 Harbor,走的都是同一套/v2/开头的 Registry API,理解了这一点,你会发现三种方案的很多操作习惯其实是一样的。

仓库存在的价值也不仅仅是“存”。它是镜像分发的中间枢纽,是团队协作的共享基础,也是生产部署的安全边界。你没仓库的时候,一个镜像要从一台机器到另一台机器,要么docker save成 tar 再传过去docker load,要么推到某个临时地方。小规模还能忍,环境一多、镜像一多,这种“人肉分发”完全不可持续。

1.2 三种仓库方案的定位对照

既然仓库这么必要,市面上可选的就三件事:用公有的、自建轻量的、自建企业级的。它们不是互相替代的关系,而是平行适用不同场景。我见过很多团队一上来就折腾 Harbor,结果配置项看得头晕;也见过团队用 Registry 用了两三年,最后连个权限控制都没有。所以在动手之前,先想清楚自己的需求在哪一档。

方案定位部署成本功能强度适合场景
Docker Hub公有 SaaS 仓库零部署,注册即可用拉取、推送、Web 管理、限流明确个人开发、学习、公开镜像分发
Registry官方轻量私有仓库一个容器即可跑起来基础拉取/推送、API 完整内网小团队、边缘节点、测试环境
Harbor企业级私有仓库依赖 Docker Compose,组件较多权限、项目、复制、扫描、审计、配额等团队协作、生产交付、合规要求高的环境

选型的核心逻辑其实就一句话:你要不要权限管理,要不要团队协作流程。只要一个人用、机器数量少,Registry 足够;只要涉及多人、多项目、要能追溯到谁推了什么,直接用 Harbor。Docker Hub 更像默认选项,它的最大价值是“开箱即用”,但拉取限流和内网访问问题往往会逼着你往自建方向走。

2. Docker Hub:最省事的公有仓库,先把镜像命名和拉取规则搞懂

2.1 镜像命名规则与推送姿势

Docker Hub 用得再频繁,也先要把镜像命名规则说清楚。一个完整的镜像 tag 通常长这样:docker.io/library/nginx:1.23。拆开看,docker.io是 registry 地址,library是命名空间,nginx是仓库名,1.23是 tag。平时你敲nginx:1.23,Docker 会默认补全docker.io/library/前缀,这是最长被人忽略的细节。

如果你要推送自己的镜像到 Docker Hub,首先得先在 Hub 上注册账号并创建一个仓库。比如账号叫zhangsan,仓库名myapp,那本地镜像先要打标签:

docker tag myapp:v1.0 zhangsan/myapp:v1.0 docker login docker push zhangsan/myapp:v1.0

docker login会提示输入用户名和密码,凭证会存在~/.docker/config.json里。注意,如果你在 CI 机器上不想交互式登录,可以用echo 'password' | docker login -u zhangsan --password-stdin这种方式。另外推送之前记得检查 tag 名称里有没有带docker.io/zhangsan/myapp,带上了也没关系,push时 Docker 会自动解析。

Hub 上有个很坑的限制是匿名拉取限流:未登录状态下每 6 小时只能拉 100 次,登录后是 200 次。听起来不少,但你在公司 NAT 后面,几十台机器共享同一个出口 IP,一天的 CI 构建很可能就把配额刷爆,报错往往是You have reached your pull rate limit。所以生产环境我从不建议依赖公共 Hub,至少应该登录状态下拉一次缓存到私有仓库。

2.2 拉取超时与网络不可达的常见处理

国内环境下,直接访问 Docker Hub 经常出现各种网络问题。最典型的就是拉镜像时报错:

docker: error during connect: Post "https://registry-1.docker.io/v2/": dial tcp: lookup registry-1.docker.io: no such host 或 could not reach the hub ([errno 101] network is unreachable). using cached c...

errno 101在 Linux 里对应的就是网络不可达(ENETUNREACH),说明你的机器根本无法访问 Hub 的地址。这个问题的本质不是 Docker 配置导致的,而是主机的路由、DNS 或者防火墙层面压根过不去。排查顺序建议是:先ping registry-1.docker.io看域名能不能解析、ICMP 通不通,再看curl -I https://registry-1.docker.io/v2/能不能拿到响应。如果 DNS 解析失败,检查/etc/resolv.conf;如果 ping 不通,检查默认路由和出口防火墙规则。没有网络回程,在 Docker 端做多少配置都白搭。

在能上网但不稳定、或访问较慢的场景下,可以给 Docker daemon 配置镜像加速。编辑/etc/docker/daemon.json

{ "registry-mirrors": ["https://your-mirror.example.com"] }

之后重启systemctl restart docker。需要注意,registry-mirrors 是让 Docker 在拉公开镜像时去镜像站拉取,但你主动docker pull mydomain/myimage这种私有地址不会走 mirror,必须配合后面的私有仓库方案。另外,不建议在daemon.json里同时配置多个无关的registry-mirrors,因为 Docker 只会挑第一个可用的,配多了反而容易让人误解。

3. 自建轻量私有仓库:Registry 三分钟跑起来

3.1 一个命令启动基础版仓库

官方提供的 Registry 镜像是registry:2,它实现的就是一套最纯粹的 /v2/ API,无 Web 界面、无权限、无项目隔离。但这恰恰是它的优点:轻、稳、资源占用极低。小团队在内网做基础镜像分发,跑一个容器就够了。

启动一个带数据持久化的 Registry:

docker run -d --name registry --restart=always \ -p 5000:5000 \ -v /opt/registry-data:/var/lib/registry \ registry:2

这里-v挂载目录必须做,否则容器一删,镜像全丢。Registry 把层数据都放在/var/lib/registry/docker/registry/v2下,我见过有人忘了挂载磁盘,容器销毁后数据全没,所以挂载这块要当成硬性要求。

起来之后怎么验证?直接在浏览器或 curl 请求它的 API:

curl http://127.0.0.1:5000/v2/_catalog

正常会返回一个 JSON,里面是仓库列表。刚开始是{"repositories":[]},因为还没推过东西。到这一步,一个最简私有仓库已经能用了,我们把本地一个镜像推上去试试。

docker tag nginx:1.23 127.0.0.1:5000/nginx:1.23 docker push 127.0.0.1:5000/nginx:1.23

如果你使用的还是默认的 Docker 配置,这条 push 大概率会报错:

http: server gave HTTP response to HTTPS client

原因后面说,这也引出了私有仓库配置里最重要的一环。

3.2 让 Docker 客户端信任私有仓库

上面那个报错很经典:Registry 默认跑的是 HTTP,而 Docker 客户端默认只跟 HTTPS 仓库通信。解决方式有两个,一是给 Registry 配 TLS 证书走 HTTPS,二是在客户端声明“这个地址我接受 HTTP”。实验环境或者内网无敏感数据的场景,最推荐的是第二种。

在每台需要访问这个私有仓库的机器上,编辑/etc/docker/daemon.json

{ "insecure-registries": ["192.168.1.10:5000"] }

这里有两个容易被忽略的细节。第一,insecure-registries里的地址必须和镜像 tag 里的 registry 地址完全一致,包括端口。你写192.168.1.10但 tag 里用192.168.1.10:5000,照样不认。第二,改完 daemon.json 后要重启 Docker 服务,而且重启前最好先验证 JSON 语法:python3 -m json.tool /etc/docker/daemon.json。一旦 JSON 格式损坏,Docker 守护进程可能直接启动失败,那个时候连docker ps都敲不动。

配置完成后,重新执行:

docker tag nginx:1.23 192.168.1.10:5000/nginx:1.23 docker push 192.168.1.10:5000/nginx:1.23 docker pull 192.168.1.10:5000/nginx:1.23

在另一台机器上也能拉取时,你的内网镜像分发链路就通了。注意,跨机器拉取时同样要在目标机器上配置insecure-registries,否则那边也会报 HTTPS 的错。

3.3 加一层认证:htpasswd 让仓库不是裸奔

裸奔的 Registry 只要知道地址,任何人都能往里推镜像。稍微正式一点的环境,至少要加一层 HTTP Basic Auth。做法很简单,用htpasswd生成一个口令文件。

用官方 httpd 镜像一行命令生成:

docker run --rm --entrypoint htpasswd httpd:2 -Bbn zhangsan 'yourpassword' > auth/htpasswd

注意-B表示 bcrypt 加密算法,比默认的 MD5 安全得多。之后重新启动 Registry,把认证文件挂载进去,并设置环境变量:

docker run -d --name registry --restart=always \ -p 5000:5000 \ -v /opt/registry-data:/var/lib/registry \ -v /opt/registry-auth:/auth \ -e "REGISTRY_AUTH=htpasswd" \ -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \ -e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \ registry:2

启动后未登录推送:

docker push 192.168.1.10:5000/nginx:1.23

会收到类似no basic auth credentials401 Unauthorized的错误。先执行docker login 192.168.1.10:5000输入账号密码,再推送就正常了。这就是 Registry 能做的最简认证方案。它没有用户注册流程,没有 Web 管理界面,账号要靠管理员手工生成,所以从这一层开始,你已经偏向 Harbor 的场景了。

4. 企业级私有仓库 Harbor:从下载、配置到跑起来的完整过程

4.1 Harbor 到底比 Registry 多做了哪些事

Harbor 最早由 VMware 开源,后来进了 CNCF 基金会,是目前企业自建镜像仓库事实上的主流选择。它不是重新造了一个 registry,而是基于 registry 做了一层很厚的封装:外面套了 nginx 做流量入口和 TLS 终止,core 组件做鉴权和项目管理,数据库存用户和权限,jobservice 做复制和清理任务,portal 提供 Web 界面,甚至还能挂 trivy 做漏洞扫描。组件多,功能自然也多。

最核心的几个能力我用自己的话说:

  • 项目级隔离:仓库从扁平结构变成项目空间,不同团队各管各的,互不可见。
  • 细粒度权限:项目内有 Guest、Developer、Maintainer、ProjectAdmin 等角色,能控制谁能拉、谁能推。
  • 镜像复制:配置一个复制规则,把内网镜像自动同步到另一个 Harbor 或外部仓库。
  • 配额管理:限制一个项目最多占多少磁盘,防止有人把仓库当网盘。
  • 回收站和 GC:删除镜像后不会立刻释放磁盘,需要手动执行垃圾回收,这个设计让不少第一次用的人踩坑。

如果你只需要“能在网页上看看镜像、控制谁能推”,Harbor 就是为这个场景准备的。但代价是部署组件多,依赖 Docker Compose,对主机的 CPU 和内存有基础要求,一般建议至少 2C4G 的机器。

4.2 离线包安装 Harbor 的完整操作

Harbor 官方提供在线安装包和离线安装包。离线包里内置了所有依赖镜像的 tar 包,安装时不需要大量拉取镜像,在公网出口受限的环境下强烈建议选 offline 版本。下载后解压:

tar zxf harbor-offline-installer-v2.11.0.tgz cd harbor

目录下会有一个harbor.yml.tmpl模板,先复制成harbor.yml

cp harbor.yml.tmpl harbor.yml

然后改配置。最关键的几个字段:

hostname: 192.168.1.10 http: port: 80 harbor_admin_password: YourStrongPassword123 database: password: YourDBPassword123 data_volume: /data

hostname是你访问 Harbor 的地址,可以是 IP 或域名,但绝对不能写http://前缀,这是配置校验失败的重灾区。harbor_admin_password是管理员初始密码,长度要足够且包含大小写。data_volume尽量指到一个容量够大的独立磁盘。

完成配置后,执行./install.sh。脚本会自动检查 docker 和 docker compose 环境,然后逐个启动组件。如果没装 docker compose 插件,Harbor 会直接报错提示,先执行:

sudo apt install docker-compose-plugin # 或者使用 docker compose v2,确认 docker compose version 有输出

安装过程最后会输出✔ ----Harbor has been installed and started successfully----。这时候用docker compose ps看容器状态:

cd /path/to/harbor && docker compose ps

你会看到harbor-coreharbor-dbharbor-jobserviceharbor-portalharbor-registrynginxredis等多个容器都在 Up 状态。浏览器访问http://192.168.1.10,默认 80 端口,用admin加刚才设置的密码登录,Harbor 的 Web 界面就出来了。

4.3 把镜像推送到 Harbor 并接入日常开发

Harbor 启动后,首件事是在 Web 界面创建一个项目,比如叫demo,访问级别可以先设为私有。然后在客户端机器上配置insecure-registries,把 Harbor 地址加进去并重启 Docker。这里要注意,Harbor 通过 nginx 暴露服务,默认 80 端口,所以地址要写成192.168.1.10192.168.1.10:80,取决于你的 daemon.json 写法,保持一致就行。

登录并推送:

docker login 192.168.1.10 docker tag myapp:v1.0 192.168.1.10/demo/myapp:v1.0 docker push 192.168.1.10/demo/myapp:v1.0

看到digest:字样的返回就说明推成功了。此时在 Web 界面进入demo项目,能看到镜像列表和对应的 tag。之后任何机器只要docker login成功,就能拉取这个项目下的镜像。

日常接入 CI 时,更推荐用 Harbor 的“机器人账户”而不是真实账号。在项目的“机器人账户”页面新建一个,系统会给一个 username 和一个以dockerconfigjson开头的 token。用法和普通登录一样:

docker login -u 'robot$demo+ci' -p 'the-token-value' 192.168.1.10

机器人账户的权限可以限制到单个项目,即使泄漏,影响面也可控。这一点在团队协作里非常实用。

5. 常见问题排查实录

5.1 关键词速查表

这些年在仓库问题上踩坑踩得实在不少,我把最常见的问题按“报错关键词”整理成一张速查表,方便按图索骥。

报错/现象可能原因处理方式
network is unreachable / errno 101宿主机网络不通、DNS 解析失败先排查路由与 DNS,再配置 mirror 或走内网仓库
http: server gave HTTP response to HTTPS client仓库是 HTTP,客户端默认走 HTTPS在 daemon.json 的 insecure-registries 增加对应地址并重启
x509: certificate signed by unknown authority自签名证书未被信任拷贝 CA 到/etc/docker/certs.d/或改用 insecure-registries
denied: requested access to the resource is denied未登录、无权限或项目不存在检查账号权限、镜像 tag 里的项目名是否正确
manifest unknowntag 不存在、推送未完成、仓库被 GC 清理确认 tag 名称和推送到正确的仓库地址
no space left on device数据盘或/var/lib/docker满了清理无用镜像、扩大数据盘
harbor failed with config validationharbor.yml 配置格式或内容不合规重点检查 hostname 是否含协议前缀、端口冲突、密码过短、YAML 缩进
413 Request Entity Too Largenginx 默认上传大小限制调整 Harbor 内置 nginx 的 client_max_body_size 配置并重启

这张表里出现频率最高的其实还是前三条。很多新手一看到 HTTPS 相关报错就以为是证书问题,拼命配证书,结果忘了检查仓库到底是不是 HTTP;一看到 network unreachable 就怀疑 Docker 有毛病,结果发现是机器网关断了。排查顺序建议永远是:网络层优先,仓库配置其次,Docker 配置最后。

5.2 三个容易让人栽跟头的深层细节

第一个坑是修改daemon.json导致的 Docker 启动失败。很多人拿 Linux 那套思维,直接vim /etc/docker/daemon.json然后一顿改,结果多打了个逗号,重启 Docker 后服务起不来,连docker ps都报cannot connect to the Docker daemon。这时候补救方法是把daemon.json先备份,然后删除或改回{},用journalctl -u docker看具体报错,修复后再重启。我建议所有改 daemon.json 前,先跑一遍python3 -m json.tool /etc/docker/daemon.json验证格式,这行命令不值钱,但能救你一个下午。

第二个坑是 Harbor 配置校验失败。报错提示harbor happened in config validation,这其实是 Harbor 在./install.sh执行时对harbor.yml做检查。最常见的死法就是把hostname写成hostname: http://192.168.1.10,Harbor 要求的是纯主机名或 IP,不带你协议名;另一个死法是https段没有启用却保留了证书文件路径,或者证书文件根本不存在;还有一种是端口 80 或 443 已经被别的进程占用。遇到这个报错不要慌,./install.sh会明确提示是哪一项校验失败,仔细读输出,基本都指向修改harbor.yml并重新执行。

第三个坑是动不动就“删除镜像,但磁盘没释放”。Harbor 的 UI 里删除一个 tag,逻辑上只是把 manifest 标记为可回收,blob 层还在磁盘上,只有执行“垃圾回收”后才能真正释放空间。如果你发现删了一堆镜像,df -h却一点没变,这是正常现象。Harbor 的垃圾回收路径在 Web 界面的“系统管理”里,执行 GC 时最好选在没有构建任务的时段,因为 GC 过程中 registry 的写入会受影响。Registry 裸仓库更明显,需要手动进入容器执行:

docker exec registry /bin/registry garbage-collect /etc/docker/registry/config.yml --delete-untagged

并且 GC 之后,仓库 API 里的_catalog列表可能还会显示残留的 repo,通常要再删掉_manifests/repositories目录或做二次清理,这是 Registry 设计上一个让人难受的地方,但了解它就不至于傻等磁盘自动变干净。

5.3 一些日常运维上的实用经验

最后分享几个我自己的实践习惯,不一定写在哪本官方文档里,但确实省过不少事。

第一个是镜像 tag 规范。我建议 tag 不要用latest,要带版本号,最好是流水号或 Git commit。latest的最大问题是不可追踪:你今天 pull 和明天 pull,拿到的可能就不是同一个东西。生产环境里我把 tag 直接打成项目名-commit号,比如order-service-7f3a2c1,出了问题能精确定位是哪一版代码构建出来的。

第二个是“推镜像前先看磁盘”。Registry 和 Harbor 的垃圾回收机制都不同于普通文件系统,删除不是立即释放,所以部署时宁可把数据盘规划大一点,也不要等到报no space left再临时清理。团队多了以后,配额功能一定要开,Harbor 项目里设个 10GB 或者 20GB 的限额,能有效防止有人堆一堆没用的镜像。

第三个是对外访问不稳定的环境,尽量把依赖外网的拉取前置。比如在 CI 构建机配置里把docker build--pull参数去掉,或者确保构建基础镜像已经预拉到本地,不然构建高峰期网络一抖,整个流水线跟着崩,那种“using cached ...”或者network unreachable的报错会把你折腾得没脾气。

从最小的 Registry 到完整的 Harbor 其实是一条很平滑的演进路线:自己一个人用,Registry 带上认证就能撑很久;团队开始正规协作,Harbor 的权限和项目隔离就是刚需;Docker Hub 则是始终存在的默认选项,适合不需要私有化的公开镜像。我自己的体会是,先把 Registry 跑通,理解 tag、推送、认证这些基本链路,再迁移到 Harbor,比一上来就部署全家桶要顺手得多。镜像分发这件事最难的从来不是命令,而是一开始就能想清楚“谁在推、谁在拉、怎么控权、怎么放数据”,把这几个问题想明白,仓库就不会成为你容器路上的瓶颈。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 21:29:58

Excel SUM求和不生效?文本型数字与隐藏行排查指南

做数据的人,十有八九都经历过这种崩溃瞬间:表格里肉眼可见全是数字,SUM(A1:A10) 一回车,结果要么是 0,要么比实际少了一大截。更气人的是,你点进单元格里看,数字明明是数字,格式也改…

作者头像 李华
网站建设 2026/9/16 21:29:53

LIBERO-Plus:面向VLA模型的鲁棒性评测框架

1. 项目概述:为什么VLA模型需要LIBERO-Plus这样的鲁棒性评测框架最近在机器人感知与决策交叉领域,VLA(Vision-Language-Action)模型正从实验室走向真实场景——不是那种调好光照、固定背景、只跑预设轨迹的Demo环境,而…

作者头像 李华
网站建设 2026/9/16 21:27:57

LLM代码翻译如何不丢语义?SAGE算法约束新范式

LLM做代码翻译,这两年几乎成了软件工程圈的标配话题。我在内部工具链项目里把Python和Java互译跑了大半年,最深的感受是:意图丢失比语法错误可怕得多。今天这篇,我想聊聊一套配合算法约束的LLM代码翻译新范式,核心思路…

作者头像 李华
网站建设 2026/9/16 21:27:48

Windows下3D Gaussian Splatting环境搭建与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 21:26:38

Flutter鸿蒙应用崩卡烫排查:从hilog到CPU Profile的完整路径

搞过 Flutter 鸿蒙开发的兄弟应该都有过这种经历:应用在模拟器上跑得好好的,发到真机上没几分钟就崩了;或者一个页面滑起来掉帧,手机热得能煎鸡蛋。最难受的不是问题本身,而是你对着 DevEco Studio 和 Android Studio …

作者头像 李华