news 2026/10/20 8:23:33

从零搭建带认证与TLS的Docker私有镜像仓库

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零搭建带认证与TLS的Docker私有镜像仓库

前阵子我给团队搭内部镜像仓库,需求很明确:几台内网服务器需要从开发机拉镜像,但公共镜像仓库在这条链路上速度不稳定,而且部分镜像带着内部构建配置,放到公开平台不合适。研究了一圈,决定自己部署一套 Docker Registry,并配上认证登录——不然服务端口一开,任何能连上这台机器的人都能把镜像拖走,这在内部环境里也是不能接受的。

这篇文章就把整个从零到能正常docker login、docker push、docker pull的完整过程写出来。不光是命令,还包括我在配置认证和 TLS 过程中反复踩过的坑。适合刚接触自建仓库、想给 Registry 加认证的开发者,也适合团队规模不大、不想直接上重型镜像管理系统的运维同学。

1. 自己部署 Registry 之前,先想清楚这三个问题

1.1 你的场景到底适不适合自建仓库

Docker Registry 是 Docker 官方的镜像存储与分发服务,简单说就是把你的镜像存在自己的服务器上,需要的时候通过客户端推送和拉取。它适合哪些场景,我总结下来是这几类:

  • 镜像要分发的目标机器都在内网,走公共仓库绕一圈,又慢又不可控;
  • 有离线或隔离网络环境,机器集体没有外网访问能力;
  • 镜像内容相对敏感,不想把内部构建产物放到公开或半公开的平台;
  • 数据主权要求高,所有镜像数据必须留在自己的存储路径上。

反过来,如果你只是一个人在开发机上折腾,或者团队已经有成熟的托管镜像服务且网络链路很好,那就没有必要自建。自建意味着镜像存储、证书、认证、备份、磁盘监控都要自己管,这个运维成本是实打实的。

1.2 为什么选官方 Registry,而不是 Harbor 这类重量级方案

当时我也对比过 Harbor。功能确实全——项目管理、镜像复制、配额限制、Webhook、漏洞扫描,界面也很完整。但它的部署依赖数据库、后端存储组件,对一台普通的内网服务器来说,光依赖关系就要理半天。而官方registry:2镜像是一个轻量容器,不依赖任何外部服务,数据目录就是一个文件夹,迁移、备份、重装都非常直接。

对于中小规模团队,用户量十几个、镜像几十上百个这种量级,官方 Registry 完全够用。它的定位就是把镜像存储和分发这件事做扎实。如果后面真需要更复杂的权限模型和多租户管理,再在它前面加一层反向代理,或者平滑迁移到 Harbor,也不是什么难事。

1.3 这篇文章最终交付什么

我会带你在一台服务器上完成四件事:第一,跑起一个数据持久化的 Registry 容器;第二,配上 TLS 证书,让传输过程加密;第三,通过 htpasswd 文件启用用户认证,让客户端必须docker login才能操作;第四,从客户端侧完整验证登录、推送、拉取,并整理常见报错的排查方法。最后会补充用户管理和日常运维相关的经验。

2. 初始搭建:先把默认配置跑通

2.1 环境准备

开始之前确认三件事。第一,Docker 版本不要太老,我建议 20.10 以上,老版本在镜像推送协议兼容性上偶尔会有怪问题。第二,磁盘空间一定要提前规划,镜像层数据会越积越多,最好给 Registry 单独准备一个数据盘或者独立分区。第三,确认服务器防火墙和安全组放行了 5000 端口,很多问题其实都出在端口不通。

检查 Docker 版本:

docker version --format '{{.Server.Version}}'

我这里测试环境的 Docker 版本是 24.0.x,下面所有命令都是基于这个版本验证的。

2.2 创建数据目录并启动容器

先建数据目录:

mkdir -p /data/registry

然后运行容器:

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

这个阶段我先不配认证,目的很简单:先把基础链路跑通,确认存储、端口、镜像推拉都正常。如果一开始就叠加认证和 TLS,后面一旦出问题,排查范围会大很多。

逐项解释一下参数含义:

  • -d:后台运行;
  • -p 5000:5000:把容器的 5000 端口映射到宿主机 5000 端口;
  • --name registry:容器名称,后续操作都用这个名称引用;
  • --restart=always:Docker 服务重启或机器重启后自动拉起容器;
  • -v /data/registry:/var/lib/registry:宿主机目录挂载到容器内的镜像数据目录,这是镜像数据持久化的关键。少了这一行,容器一删镜像全丢。

2.3 验证基础功能

容器启动后,先在服务器本机验证 API 是否正常:

curl http://localhost:5000/v2/_catalog

正常会返回{"repositories":[]},说明服务已经在响应了。

接着从公共仓库拉一个小镜像做推送测试:

docker pull alpine:3.19 docker tag alpine:3.19 localhost:5000/demo/alpine:3.19 docker push localhost:5000/demo/alpine:3.19

如果推送成功,再查一下仓库列表:

curl http://localhost:5000/v2/_catalog

应该能看到{"repositories":["demo/alpine"]}。这一步做完,基础链路就确认没问题了。我强烈建议这个阶段不要跳过,因为后续加完认证和 TLS 之后,如果推送失败,你就可以把问题范围缩小到认证或证书上,而不是怀疑存储和网络。

3. 认证与 TLS:从裸奔到安全层完备

3.1 为什么必须先上 TLS 再谈认证

Registry 默认的认证方式是 HTTP Basic Auth,用户名密码本质上只是 Base64 编码后放在请求头里,并没有加密。如果走明文 HTTP 传输,密码基本等于裸奔。内网环境也未必绝对安全,所以先把传输层加密搞定,再启用认证,顺序不能反。

另外还有一个技术层面的原因:Docker 客户端默认对非 localhost 的 Registry 强制要求 HTTPS。也就是说,如果你不配 TLS,客户端访问你的仓库时会直接报错,或者你得在客户端做一堆insecure-registries之类的特殊配置。先上 TLS,客户端侧的行为才符合 Docker 的默认预期。

3.2 准备 TLS 证书

如果有域名,最方便的做法是申请免费证书服务签发的证书,配置好域名解析后,证书的申请和续期都可以自动化。我这里演示用域名registry.example.com。

如果只有 IP 没有域名,可以用自签证书。生成命令如下:

mkdir -p /data/registry/certs openssl req -newkey rsa:2048 -nodes \ -keyout /data/registry/certs/server.key \ -x509 -days 365 \ -out /data/registry/certs/server.crt \ -subj "/CN=registry.example.com" \ -addext "subjectAltName=DNS:registry.example.com,IP:192.168.1.10,IP:127.0.0.1"

这段命令生成一对自签证书和私钥,有效期 365 天。注意subjectAltName要同时写上域名、服务器 IP 和本机回环地址,否则客户端访问时会报证书和主机名不匹配。生成后检查一下私钥权限,server.key建议设为 600:

chmod 600 /data/registry/certs/server.key

自签证书的缺点是客户端默认不信任它,后面我会在客户端配置部分展开说明。

3.3 生成 htpasswd 用户文件

Registry 的认证文件格式是标准的 htpasswd 文件,每行一个用户,格式是用户名:密码散列。生成工具不用额外安装,直接用 httpd 镜像就能完成:

mkdir -p /data/registry/auth docker run --rm httpd:2.4 htpasswd -Bbn myuser mypassword > /data/registry/auth/htpasswd

这里几个参数解释一下:

  • -B:强制使用 bcrypt 算法,比默认的 MD5 变体更安全;
  • -b:允许在命令行直接传密码;
  • -n:不写文件,结果输出到标准输出,方便我们重定向到指定位置。

生成后可以cat看一下内容,应该是类似myuser:$2y$05$...的一行。密码不是明文,这很重要——htpasswd 文件本身就算泄露,对方也不能直接还原出原始密码。

注意:上面的>是覆盖写。如果后续要追加第二个用户,务必改成>>:

docker run --rm httpd:2.4 htpasswd -Bbn user2 pass2 >> /data/registry/auth/htpasswd

我一开始就吃过这个亏,追加用户时用了单箭头,结果把第一个用户覆盖了,导致第一个账号直接登录失败。

3.4 用环境变量启用认证和 TLS

Registry 2.x 支持通过环境变量覆盖默认配置项,相比自己写一个完整的config.yml再挂载进去,这种方式更直观,也少一个文件要维护。我把之前的容器删掉,重新创建:

docker rm -f registry docker run -d \ -p 5000:5000 \ --name registry \ --restart=always \ -v /data/registry:/var/lib/registry \ -v /data/registry/auth:/auth \ -v /data/registry/certs:/certs \ -e REGISTRY_AUTH=htpasswd \ -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \ -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \ -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/server.crt \ -e REGISTRY_HTTP_TLS_KEY=/certs/server.key \ registry:2

环境变量的逻辑对应关系是:REGISTRY_AUTH=htpasswd开启认证,REGISTRY_AUTH_HTPASSWD_REALM定义认证领域名称,REGISTRY_AUTH_HTPASSWD_PATH指定容器内的 htpasswd 文件路径,REGISTRY_HTTP_TLS_CERTIFICATE和REGISTRY_HTTP_TLS_KEY分别指定证书和私钥在容器内的路径。

挂载方面,我之前已经把宿主机/data/registry挂到容器的/var/lib/registry,现在又把宿主机的/data/registry/auth挂到容器内/auth,把/data/registry/certs挂到容器内/certs。这样所有的配置文件和数据都集中在/data/registry下面,管理、备份都很方便。

3.5 验证认证是否真正生效

启动新容器后,先不带凭据访问 API:

curl -k https://localhost:5000/v2/

-k是跳过证书校验,因为自签证书在 curl 这里也不被信任。无凭据访问应该会收到401 Unauthorized的响应,这就说明认证已经生效。

再带用户名密码访问:

curl -k -u myuser:mypassword https://localhost:5000/v2/

正常返回{}说明认证通过,API 可以正常访问。到这一步,Registry 侧的工作就基本完成了。

4. 客户端登录的完整链路:配置、推拉、报错处理

4.1 客户端侧的前置条件

首先确保客户端机器能解析到 Registry 的地址。有域名就配 DNS,没有域名可以直接在/etc/hosts里加一行:

192.168.1.10 registry.example.com

然后要处理证书信任的问题。如果你用的是免费证书服务签发的受信任证书,这一步什么都不用做。如果是自签证书,有两种选择:

  • 把自签证书加入客户端系统的证书信任链,这样 Docker 会认为它是合法证书;
  • 在 Docker 配置里把该地址加入insecure-registries,跳过证书校验。

第二种方式配置简单,但相当于告诉 Docker“这个地址不用验证证书”,安全性略低,只建议在内网环境使用。

4.2 配置 insecure-registries(针对自签证书场景)

编辑客户端的 Docker 配置文件/etc/docker/daemon.json:

{ "insecure-registries": ["registry.example.com:5000"] }

然后重启 Docker:

sudo systemctl restart docker

这里有一个很重要的提醒:重启 Docker 会导致本机所有容器全部中断。如果这台客户端机器上跑着其他服务,一定要在业务低峰期操作,或者先确认容器都有restart策略能自动拉起。

4.3 docker login 与推拉镜像实测

登录:

docker login registry.example.com:5000

按提示输入用户名myuser和密码,看到Login Succeeded就说明认证链路通了。登录成功后,凭证会保存在~/.docker/config.json里,后续推送和拉取就不需要反复输入密码。

给镜像打标签并推送:

docker tag localhost:5000/demo/alpine:3.19 registry.example.com:5000/demo/alpine:3.19 docker push registry.example.com:5000/demo/alpine:3.19

镜像标签里的仓库地址必须和登录地址完全一致,否则 Docker 会认为这是另一个仓库,从而触发重新登录或报错。

拉取镜像:

docker pull registry.example.com:5000/demo/alpine:3.19

完整走一遍之后,这套流程就算跑通了。

4.4 常见报错与排查对照

我在配置过程中遇到的报错基本可以整理成这么一张表:

报错信息可能原因解决办法
http: server gave HTTP response to HTTPS client客户端没把地址加入insecure-registries,或者 Registry 的 TLS 没生效在daemon.json中配置insecure-registries后重启 Docker,或检查容器内的证书环境变量
x509: certificate signed by unknown authority自签证书未加入客户端信任链把server.crt添加到系统证书信任目录,或使用insecure-registries
dial tcp ... connection refused端口不通,防火墙或安全组拦截检查服务器防火墙规则、安全组策略,确认容器正在运行且端口映射正常
login attempt to unknown realm认证配置未生效,或客户端访问的不是配置认证的入口检查REGISTRY_AUTH相关环境变量是否正确配置,docker restart 后重试

排查这类问题,我的习惯是先看服务端容器日志:

docker logs registry --tail 50

日志里的信息往往比客户端报错更接近问题根源,比瞎猜高效得多。

5. 用户与密码管理:动态增删、轮换与权限控制

5.1 htpasswd 的工作机制,值得你多花两分钟理解

Registry 的认证模型非常朴素:所有用户和密码散列存放在同一个 htpasswd 文件里,每行用户名:散列值。客户端发起docker login时,Docker 会先请求一个未认证的 API,Registry 返回401并带WWW-Authenticate头,客户端随后用用户名密码重试,Registry 通过 htpasswd 文件校验。

这个机制的好处是简单、可靠、不依赖任何外部组件。坏处是权限粒度比较粗——Registry 本身不区分“只读用户”和“读写用户”,只要密码正确,就能拉取和推送这个仓库里的全部镜像。所以权限控制更多要靠账号管理来实现:一个项目一个账号,密码泄露了单独禁用一个,而不是所有团队成员共用一个账号。

5.2 添加用户、删除用户和更新密码

添加新用户:

docker run --rm httpd:2.4 htpasswd -Bbn newuser newpassword >> /data/registry/auth/htpasswd

删除用户,打开 htpasswd 文件删掉那一行即可:

vi /data/registry/auth/htpasswd

更新密码,用交互式命令最稳妥,它会提示输入两次新密码,避免命令行里留下历史记录:

docker run -it --rm -v /data/registry/auth:/auth httpd:2.4 htpasswd -B /auth/htpasswd myuser

修改完 htpasswd 文件后,重启容器让配置生效:

docker restart registry

不需要重新创建容器,因为 htpasswd 文件是通过挂载方式进入容器内部的,改动会直接映射进去。

注意:htpasswd 文件是单向散列,密码一旦丢失无法找回,只能重置。

5.3 与 CI/CD 集成的经验

自动构建流程要往这个私有仓库推镜像,最常见的做法是专门创建一个机器人账号,比如ci-bot,把账号密码放到 CI 系统的 Secret 配置里。构建阶段先执行docker login,再docker push。

这里有一个容易忽略的细节:docker login成功后,明文凭证会保存在 CI 运行用户家目录下的~/.docker/config.json中。如果 CI 机器是多租户共用的,或者构建产物会被别人打包带走,这个文件就等于把密码暴露了。更稳的做法是不要在文件系统里留凭证,而是每次构建前用docker login --password-stdin从环境变量读取密码,用完即走:

echo "$REGISTRY_PASSWORD" | docker login registry.example.com:5000 -u ci-bot --password-stdin

5.4 前置反向代理时要注意请求头

实际部署中,很多人不会直接用 5000 端口对外,而是在前面挂一层 Nginx 反向代理,把域名 443 端口转发到 Registry 容器的 5000 端口。这个方案本身没问题,但要特别注意代理配置里必须保留Authorization请求头和Docker-Distribution-Api-Version请求头,否则客户端登录时认证信息会被吞掉,表现为反复提示登录失败。

如果你在自己的代理配置里开了什么“过滤敏感头”之类的规则,优先排查这里。

6. 日常运维经验:备份、清理与故障恢复

6.1 数据备份与恢复

Registry 的所有镜像数据都在/data/registry目录下,备份思路很简单。为了保证一致性,最可靠的方式是短暂停容器再打包:

docker stop registry tar -czf /data/registry-backup-$(date +%F).tar.gz -C /data/registry . docker start registry

虽然会带来几十秒的停机,但镜像仓库的写入频率通常不高,选在低峰期操作影响很小。如果要恢复,在目标机器上解压到相同路径,重新执行一次docker run命令即可。我实际做过一次迁移,整个过程不到十分钟。

6.2 删除镜像后磁盘空间不释放的问题

这是我遇到过最隐蔽的坑。通过 API 删除镜像 manifest 之后,你会发现磁盘空间基本没变。原因是 Registry 删除镜像时,只是标记了 manifest,底层那些不再被引用的镜像层 blob 并不会立刻删除,需要手动执行垃圾回收:

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

执行过程中 Registry 会暂时阻塞写入操作,所以建议在业务低峰期跑。日志里会输出一大堆deleted记录,执行完再查磁盘就能看到空间释放了。

6.3 用户密码忘了怎么办

htpasswd 文件的散列是不可逆的,忘密码唯一的路就是重置。用 5.2 里的交互式命令重新设置新密码,然后重启容器:

docker restart registry

这个操作最好由管理员统一执行,避免多个用户不知道已经改了密码,反复尝试登录把服务端日志刷出大量认证失败记录。

6.4 磁盘空间监控一定是提前做

自建 Registry 最常见的故障不是服务挂掉,而是磁盘写满。镜像仓库的磁盘增长是持续的,等到docker push开始报no space left on device时,基本就是事故状态了。建议:

  • 给 Registry 配独立数据盘,避免和系统日志等其他数据抢占空间;
  • 定期执行du -sh /data/registry观察增长趋势;
  • 给磁盘使用率加监控告警,阈值建议设在 80% 左右。

如果已经满了,处理顺序是:先删掉确定不用的镜像,再执行 GC 回收层数据,最后确认空间释放情况。

最后说一个我自己的习惯:把上面的docker run命令、证书生成命令、htpasswd 生成命令全部写成脚本放到服务器上。这样不管是换机器、完整重装,还是单纯忘了当初怎么配置的,看一眼脚本就全明白了。自建 Registry 本身的难度并不高,真正容易出问题的反而是认证、TLS、备份、清理这些平时不会天天碰的细节。按上面这个顺序完整走一遍,至少能少踩一半的坑。

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

微服务零信任改造:UKey硬件与mTLS双向认证实战解析

微服务这东西,拆的时候很爽。等业务被拆成三四十个服务以后,你会发现最头疼的不是服务发现,不是分布式事务,而是服务之间到底该怎么信任彼此。我前两年接手过一个内部系统改造,几十个服务跑在同一套内网里,…

作者头像 李华
网站建设 2026/10/20 8:23:13

Agent技能不是越多越好:少即是多,如何科学配置技能

1. 先泼盆冷水:Skill 数量与 Agent 智商不成正比1.1 我见过最离谱的“技能全家桶”先讲一个真实的场景。某团队在内部系统里接入 Agent 时,产品经理兴冲冲地列出了二十多个“必备技能”:查工时的、查 OKR 的、查会议室状态的、生成周报的、翻…

作者头像 李华
网站建设 2026/10/20 8:21:33

前端进阶实战:数据去重、并发控制与表格排序的完整实现

刚把3.1阶段的进阶13、进阶14、进阶15三道题做完,趁热记录一下。这个3.1指的是我最近在跟的一套前端进阶训练营里的第三章第一节,前面1.x和2.x主要练基础语法和核心API,到了3.1开始上综合题。进阶13、14、15刚好是一组“数据加工、异步控制、…

作者头像 李华
网站建设 2026/10/20 8:20:18

高可用分布式系统设计与选型:从SLA指标到架构落地的完整指南

高可用这个话题,做架构的人天天挂在嘴边,可真到了设计系统的时候,很多人拿出的方案还是“多部署几台机器”“数据库做主从”这种原始思路。我在一线做了这么多年架构设计和系统调优,亲眼见过太多系统在流量高峰期轰然倒塌的案例&a…

作者头像 李华
网站建设 2026/10/20 8:20:03

手机烧录实战:MTK/高通/展锐三平台工具与避坑指南

简介:这是一份面向设备维修工程师、嵌入式开发者和有一定刷机经验玩家的多平台固件烧录工具集,聚焦 MTK、高通、展锐三家的系统版本升级、线刷与救砖场景。包内围绕 SP Flash Tool、QFIL、展锐专用刷机工具等常见刷机方案,整理了配套驱动、配…

作者头像 李华