ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Docker私有仓库搭建全攻略:从最简部署到认证与HTTPS

Docker私有仓库搭建全攻略:从最简部署到认证与HTTPS 最早让我决定搭一个 docker 私有仓库是因为一件特别烦的事项目里定制过的镜像散落在各个开发机的本地换台电脑就要重新 build 一遍后来团队加了个新同事光是把几个基础镜像从老同事电脑上拷到新机器上就折腾了半天。与其继续这么原始地靠移动硬盘和聊天记录传镜像不如花十分钟把私有仓库跑起来。这篇文章就围绕 docker 快速部署私有仓库这件事把从最简部署到加认证、配置 HTTPS 的完整过程梳理一遍适合已经装好 docker 但还没用过 registry 的朋友参考。看完之后你至少能自己搭一个内网可用的仓库并且知道后面遇到问题该去哪排查。1. 先想清楚私有仓库解决什么问题registry 和 repository 别搞混1.1 什么时候你需要一个私有仓库很多人一上来就问怎么搭私有仓库问得多了我发现大部分人其实对自己的需求很模糊。所以先别急着敲命令我们花两分钟看看它到底解决什么痛点。第一个典型场景是团队内部共享镜像。公司的开发环境一般有多台机器你们很可能基于某个基础镜像做了定制比如打好了补丁的 JDK 镜像、装好了常用依赖的 Python 运行环境、内部封装好的 Node 基础镜像。如果每次都在每台机器上重新 build一方面浪费时间另一方面很难保证每台机器 build 出来的东西完全一致。把定制的镜像推到私有仓库之后任何一台机器 docker pull 一下就是同一个版本这个一致性价值在多人协作时特别明显。第二个典型场景是内网或者离线环境。很多公司的开发服务器不能访问外网或者访问 Docker Hub 慢得离谱。这时候你可以在能联网的机器上把需要的镜像 pull 下来然后 push 到内网的私有仓库内网机器统一从私有仓库拉取。我实际测过内网环境下从私有仓库拉镜像速度基本取决于交换机和磁盘比从公网拉快了不止一个量级。第三个场景是镜像内容不想公开。比如内部项目带敏感配置的镜像、定制了密钥的部署镜像推到公共仓库显然不合适哪怕设成私有账号也有被扒的风险。在自己可控的服务器上部署私有仓库镜像只在内部网络流转可控性高很多。如果你只是一个人在自己电脑上玩 docker而且所有镜像都能直接从公共仓库拉那确实不一定需要私有仓库。但只要开始跟团队协作、有多台服务器、或者要交付一套内网环境私有仓库基本是绕不开的一环。别急着上重型方案先从轻量的 registry:2 开始跑起来等真的遇到管理上的痛点再升级这个节奏是最舒服的。1.2 三个关键概念image、repository、registry动手之前我先把三个词理清楚因为很多人在第一次接触私有仓库时就是被这三个词搞晕的。image 是镜像也就是 docker images 命令列出的那些东西比如 nginx:latest、ubuntu:22.04。repository 在 docker 语境下指的是同一个镜像名下的所有 tag 的集合。比如 nginx 这个镜像名后面可以跟 nginx:1.25、nginx:1.26、nginx:latest 这些 tag它们共同属于 nginx 这个 repository。registry 是镜像仓库服务它用来存放和管理若干个 repository。Docker Hub 就是一个公共的 registryregistry:2 官方镜像跑起来的也是一个 registry。一个不太严谨但很好记的类比registry 相当于一个网盘账号网盘里的每个文件夹相当于一个 repository文件夹里的不同版本文件相当于不同 tag 的镜像。网盘提供上传和下载registry 做的就是镜像的上传和下载并且把这一切标准化成 Docker 客户端可以直接使用的协议。理解了这三个概念后面所有操作就顺了部署 registry 是搭好网盘push 是把镜像文件传上网盘pull 是从网盘下载登录取决于你的配置。千万不要把 repository 和 registry 混着叫否则看文档的时候很容易被绕进去。2. 部署前的准备工作和方案选型2.1 前置条件Docker 装好没有这一步看起来最简单但其实最容易卡住。如果你的机器上还没有 docker先把 docker 装好再回来不同系统差异比较大我简单分三类说。Linux 上是相对省心的ubuntu 和 centos 都有各自的安装教程通用做法是用官方脚本安装一条命令就能搞定。装完之后建议把当前用户加进 docker 用户组否则每次执行 docker 命令都要加 sudo非常影响体验。加完组记得重新登录或者执行 newgrp docker 让权限生效。装好之后先在终端跑一个 docker version看到 Client 和 Server 两段输出就说明 docker 已经在正常工作了。如果只有 Client 没有 Server那说明服务没启动Linux 上执行 systemctl start docker 即可。Windows 上一般是用 Docker Desktop安装过程本身不复杂但经常会遇到一个报错virtualization support not detected 或者 failed to start because virtualisation support wasnt detected。这种情况十有八九是 BIOS 里的虚拟化没开或者 Windows 的 Hyper-V、WSL2 功能没启用。解决方法是重启进 BIOS开启 Intel VT-x 或者 AMD-V然后在启用或关闭 Windows 功能里勾上 Hyper-V 和适用于 Linux 的 Windows 子系统再重启电脑。Docker Desktop 启动正常之后在 PowerShell 里执行 docker version 同样能验证。提示私有仓库本身对硬件要求很低1 核 1G 的虚拟机或者一台普通 PC 都能跑。真正的瓶颈在存储和网络带宽这两个后面会展开讲。2.2 用官方 registry 还是 Harbor快速部署场景怎么选私有仓库的成熟方案不少最常见的是两个Docker 官方提供的 registry 镜像以及功能完整的企业级 Harbor。registry:2 官方镜像非常轻量核心就是一套 registry 服务端部署极其简单拉下来一个容器就能用。它的定位是够用支持镜像的 push/pull、支持 htpasswd 认证、支持多种存储后端但是不带网页 UI镜像的浏览和删除要用命令行或者 API 来完成。对于只想在十分钟内把私有仓库跑起来的场景这显然是最优选。Harbor 是基于 registry 做的一套企业级方案带 Web 管理界面、完整的用户权限体系、镜像复制和漏洞扫描功能。功能确实全面但部署相对重依赖 docker compose 或者 helm对一个小团队来说多少有点杀鸡用牛刀。我见过一些团队一上来就上 Harbor结果运维成本远超预期最后反而没人维护。我个人的建议很直接如果需求是快速部署一个私有仓库用 registry:2 就对了。十分钟能跑起来后面加认证也是改两行配置的事。等团队规模大到需要多角色权限管理、需要可视化操作镜像的时候再迁移到 Harbor 也不迟而且 Docker 私有仓库的数据迁移并不复杂。3. 十分钟跑起一个可用的私有仓库3.1 最简部署一行 docker run 搞定先别想太复杂最快的方式就是一条命令。假设你的机器已经装好 docker直接在终端执行docker run -d -p 5000:5000 --name registry registry:2这条命令拆开来看-d 表示后台运行-p 5000:5000 把容器内的 5000 端口映射到宿主机的 5000 端口--name registry 给容器起个名字registry:2 是官方镜像名和标签。registry 容器默认监听 5000 端口这是它提供 HTTP API 的端口docker push 和 pull 就是通过这个端口通信。为什么约定俗成用 5000因为这是 registry 最常用的默认端口而且不容易跟 80、8080 这些常见端口冲突所以业界基本都沿用下来了。跑起来之后先 docker ps 看一眼容器状态只要 registry 容器是 Up 状态私有仓库就算在运行了。这时候用 curl 快速验证一下 HTTP 接口确认服务真的在响应返回一个空 JSON 对象 {} 就代表正常curl http://localhost:5000/v2/这一步虽然不起眼但真心建议养成习惯后面排查任何问题都要先从这个接口看起。不过一行命令的部署方式有个明显问题容器一旦被删除里面的镜像数据就跟着没了。所以紧接着就需要用 docker compose 把数据和配置挂载到宿主机上这才算真正可用。3.2 用 docker compose 管理生产环境更省心docker compose 本质上就是把 docker run 的各种参数写进一个 yaml 文件好处是配置可视化、可以纳入版本管理、可以一键启停。部署私有仓库我强烈建议直接用这种方式后面加认证、改配置都只需要改 yaml 然后重启。先建一个项目目录比如 /opt/registry在目录下创建 docker-compose.ymlversion: 3.8 services: registry: image: registry:2 container_name: registry restart: always ports: - 5000:5000 volumes: - ./data:/var/lib/registry每个配置项背后都有讲究。image 指定使用官方镜像restart: always 表示容器异常退出后自动重启这个对服务类容器极其重要不加的话宿主机一重启 registry 就没了后面所有应用都拉不到镜像ports 是做端口映射volumes 把容器内的 /var/lib/registry 挂载到宿主机的 ./data 目录这样镜像数据就持久化在宿主机上了容器删了重建数据也不会丢。在目录下执行 docker compose up -d它会自动拉取 registry:2 镜像并启动容器。启动后 docker ps 确认状态正常就能看到 registry 容器处于 Up。以后想查看日志用 docker logs -f registry想停止服务用 docker compose down想重新加载配置就先 down 再 up。注意如果宿主机上已有服务占用了 5000 端口compose 启动会报地址冲突。解决办法是改宿主机映射端口比如 5001:5000客户端访问时对应改成 5001 即可容器内部端口不用动。3.3 部署完先自查仓库到底通没通部署完成不等于已经能用我习惯做三个快速验证确保环境真的没问题。第一验证 HTTP 接口。curl http://localhost:5000/v2/有响应就说明 registry 服务本身是通的。第二验证 API 调用。curl http://localhost:5000/v2/_catalog刚部署完没有任何镜像应该返回 {repositories:[]}。第三从本机推一个镜像进去做实测。先拉一个小镜像比如 busybox然后打标签推送docker pull busybox:latest docker tag busybox:latest localhost:5000/busybox:latest docker push localhost:5000/busybox:latest推送成功之后再 curl 一下 _catalog 接口如果 repositories 里出现 busybox说明整个链路已经走通了。这三个验证做完私有仓库基本算是可用的。但注意这里我用的是 localhost 推送只在本机验证。如果其他机器要访问这台服务器上的仓库还需要处理后面要讲的 insecure-registries 配置。4. 推拉镜像的完整流程与关键配置4.1 给镜像打标签再推送推送到私有仓库和推送到 Docker Hub 的流程一样核心就是先打标签再推送但标签的命名要理解清楚。docker 镜像推送时镜像名格式是 [仓库地址]/[仓库名]:[标签]。比如 localhost:5000/busybox:latest前面的 localhost:5000 就是 registry 的地址busybox 是 repository 名字latest 是 tag。正因为镜像名里带了 registry 地址docker 才能识别出要往哪个 registry 推。所以从公共仓库拉下来的镜像比如 nginx:latest直接 docker push 是不会推到私有仓库的因为它的镜像名里没有私有仓库的地址。需要先给它增加一个带地址的标签docker tag nginx:latest 192.168.1.100:5000/nginx:latest这里的 192.168.1.100 是你服务器的 IP实际使用替换成你自己的内网 IP 或者域名。打标签不会复制镜像数据只是给镜像增加了一个别名所以速度非常快瞬间完成。打完标签就可以推送docker push 192.168.1.100:5000/nginx:latest推送过程中如果出现 HTTP/HTTPS 相关的报错先别慌这是接下来马上要讲的一个典型配置问题。4.2 客户端怎么拉私有仓库的镜像推送到服务器上的镜像其他机器怎么拉取呢其实很简单在另一台装好 docker 的机器上执行docker pull 192.168.1.100:5000/nginx:latest但这时候会碰到一个非常经典的问题docker 客户端默认使用 HTTPS 跟 registry 通信。如果你搭的是 HTTP 的 registry 且没配证书docker pull 直接会报错Error response from daemon: Get https://192.168.1.100:5000/v2/: http: server gave HTTP response to HTTPS client解决方法是告诉 docker 客户端这个地址是允许走 HTTP 的非安全仓库。在 Linux 的 /etc/docker/daemon.json 里配置{ insecure-registries: [192.168.1.100:5000] }改完保存后重启 dockersudo systemctl restart dockerWindows 上用 Docker Desktop 的话不用去改 daemon.json 文件在 Settings - Docker Engine 里把同样的 JSON 粘贴进去然后点 Apply Restart 即可。本质上改的是同一个配置文件只是入口不同。配置完之后再执行 docker pull 就能成功了。我在实际项目中内网环境就是这么用的稳定性完全没问题。如果后续需要更安全的 HTTPS 访问则要给 registry 配置证书这个在最后一节再展开。提示insecure-registries 是数组可以写多个地址。团队里每台需要访问私有仓库的机器都要加这个配置否则各自都会报同样的 HTTPS 错误。如果你之前配过镜像加速源记得两个配置要合并到同一个 JSON 对象里不要覆盖掉原来的内容。4.3 把认证加上别让仓库裸奔前面部署的 registry 是完全开放的没有认证任何人都能通过 5000 端口访问你的仓库并随意 push 或 pull 镜像。在内网里可能一时看不出问题但一旦镜像被误覆盖或者被塞入奇怪的东西排查起来非常痛苦。registry:2 官方支持 htpasswd 认证配置也不复杂。第一步生成用户密码文件mkdir -p auth docker run --rm --entrypoint htpasswd registry:2 -Bbn admin yourpassword auth/htpasswd这条命令里 --rm 表示容器运行完就删除--entrypoint htpasswd 表示覆盖镜像默认入口直接执行 htpasswd 命令-B 表示用 bcrypt 算法加密-b 表示在命令行直接提供密码-n 表示结果输出到标准输出而不是写文件。执行完 auth/htpasswd 里就会多一行内容格式类似 admin:$2y$05$xxx。然后在 docker-compose.yml 里加上认证相关的环境变量version: 3.8 services: registry: image: registry:2 container_name: registry restart: always ports: - 5000:5000 volumes: - ./data:/var/lib/registry - ./auth:/auth environment: REGISTRY_AUTH: htpasswd REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd三个环境变量的作用分别是REGISTRY_AUTH 指定认证方式为 htpasswdREGISTRY_AUTH_HTPASSWD_PATH 指定密码文件在容器内的路径REGISTRY_AUTH_HTPASSWD_REALM 是认证提示信息填什么都可以。改完配置后重新创建容器docker compose down docker compose up -d之后推拉镜像前需要先登录登录成功后凭证会存在 docker 的本地配置里一段时间内不需要重复输入docker login 192.168.1.100:5000 -u admin -p yourpassword如果需要多个人使用就多生成几个 htpasswd 用户把需要的用户都写进 auth/htpasswd 即可。需要删除用户就执行 htpasswd -D auth/htpasswd username或者直接编辑文件删除对应行。这个认证方案虽然简单但足够支撑几十人团队的内网使用。5. 私有仓库日常运维与常见问题排查5.1 push 报 HTTP/HTTPS 客户端的坑这个报错可以说是我见过最多的一个。push 或者 pull 时终端里出现这种信息error: failed to do request: Head https://192.168.1.100:5000/v2/nginx/manifests/latest: http: server gave HTTP response to HTTPS client原因已经说过docker 默认用 HTTPS 跟 registry 通信而你部署的 registry 是 HTTP。解决办法就是在客户端机器的 daemon.json 里把该地址加入 insecure-registries。但有几个细节容易踩坑我单独列一下。第一个细节改完 daemon.json 必须重启 docker否则配置不生效。Linux 上执行 systemctl restart dockerWindows 上在 Docker Engine 页面点 Apply Restart。很多人改完文件忘了重启然后反复确认配置是不是写错了其实只是没重启而已。第二个细节daemon.json 里可能已经存在其他配置比如你之前配过镜像加速源那就不能覆盖原有内容而是要合并成一个 JSON 对象。我见过有人直接把这个文件改成只有 insecure-registries 一段结果之前的加速配置全丢了拉公共镜像又变回龟速排查了半天才发现是这里的问题。第三个细节如果你有多台客户端机器每一台的 daemon.json 都要加上 insecure-registries因为这个问题发生在客户端侧服务端无需做任何修改。团队里推广的时候最好把这个配置写进一份初始化脚本里新机器一键执行。5.2 认证报错与 htpasswd 权限问题加了认证后常见的报错是登录失败Error response from daemon: login attempt to http://192.168.1.100:5000/v2/ failed with status: 401 Unauthorized这个 401 绝大多数情况是账号密码不对但有一种隐蔽情况不能忽略htpasswd 文件的权限问题。registry 容器内的进程运行在特定的系统用户下如果 auth/htpasswd 文件权限太严容器内进程读不了它会导致登录时无论输什么密码都失败。处理方式是确保文件权限至少是 644chmod 644 auth/htpasswd另外如果你是在 Windows 上生成 htpasswd 文件再拷贝到 Linux 服务器的要注意换行符。Windows 的记事本默认使用 CRLF 换行可能导致 htpasswd 解析异常。解决办法是用 VS Code 或 notepad 把换行符转成 LF或者干脆在 Linux 服务器上直接生成这个文件。还有个容易被忽略的小事htpasswd 文件里的密码是 bcrypt 加密的不会出现明文。所以就算文件内容被人看到密码也不会直接泄露。但即便如此文件权限还是按最小权限原则设置比较稳妥。5.3 磁盘空间和镜像清理registry 的镜像数据都存在宿主机挂载目录里默认容器内路径是 /var/lib/registry实际宿主机路径取决于你挂载到哪里比如 /opt/registry/data。运行时间一长这个目录会明显膨胀尤其是团队高频推送新版本的时候。先用 API 看一下当前仓库里有哪些镜像curl http://localhost:5000/v2/_catalog查看某个镜像下有哪些 tagcurl http://localhost:5000/v2/nginx/tags/list如果要删除某个镜像registry 官方 API 支持 DELETE 接口但要注意它删除的是镜像的 manifest 和元数据引用并不直接删除磁盘上的数据块。删除之后还需要执行垃圾回收才能真正释放空间docker exec registry bin/registry garbage-collect /etc/docker/registry/config.yml这条命令会遍历所有仓库把没有任何 manifest 引用的 blob 清理掉。镜像多的时候可能执行几十秒到几分钟期间 registry 服务不受影响但为了稳妥还是建议在业务低峰期执行。注意如果 compose 配置里没有设置 REGISTRY_STORAGE_DELETE_ENABLEDtrueAPI 的 DELETE 接口会返回 405。要在启动时就加上这个环境变量才能通过 API 删除镜像。删镜像的命令通常是用 curl -I 先拿到 manifest 的 digest再带 DELETE 头请求这个过程对新手来说略繁琐但确实是最正统的方式。如果嫌麻烦也可以直接在宿主机上删掉 data 目录下对应的子目录不过这样操作风险较高不推荐。6. 从快速部署到长期使用的一些经验6.1 存储与备份策略私有仓库跑起来后最值得关注的是数据安全。镜像虽然可以被重新构建但那些从公共仓库拉下来再二次加工的镜像重建成本并不低。registry 的数据都在挂载目录里所以备份思路很直接定期把 data 目录打包或者用 rsync 同步到另一台机器。我在团队里就是用一个简单的 cron 任务每天凌晨把 data 目录打包后传到备份机这个方案投入极低但关键时刻很救命。如果团队有条件还可以把 registry 的存储后端切成对象存储比如 S3 或者兼容 S3 的 OSS这样数据天然有冗余。配置方法是在环境变量里指定存储类型和访问密钥。不过对于大多数场景本地磁盘加定时备份已经完全够用不用一上来就上对象存储。还有一点容易被忽略只要 volumes 挂载正确容器删除重建并不会丢数据。所以日常运维中不用太担心重启或者升级 registry 容器的操作大胆做就行。6.2 给仓库配个网页 UIregistry:2 没有自带 UI日常查看镜像只能 curl 调 API用起来确实不够直观。我在团队里搭好仓库后顺手配了一个开源的 docker-registry-ui用起来一下子方便了很多大家可以像逛网站一样浏览镜像列表和 tag。部署方式同样轻量在 docker-compose.yml 里再加一个服务用一个独立的镜像起前端页面通过环境变量指定要连的 registry 地址ui: image: joxit/docker-registry-ui:latest ports: - 8080:80 environment: REGISTRY_TITLE: My Private Registry REGISTRY_URL: http://registry:5000 depends_on: - registry这里最关键的是 REGISTRY_URL 要指向 registry 容器。在同一个 compose 网络里可以直接用服务名 registry 来访问不需要写宿主机 IP。启动之后浏览器访问 http://服务器IP:8080 就能看到网页界面支持浏览仓库、查看 tag部分场景下还可以在网页上删除镜像。对团队内部使用来说有这样一个 UI 体验会好很多。如果你不想多部署一个服务直接用 curl 调 API 也完全够用UI 只是锦上添花。看团队的接受度决定就好。6.3 我在实际使用中的几点体会最后说几句实操体会这些都是一路踩坑踩出来的经验。第一私有仓库的部署不难难的是长期稳定运行。我见过不少同事把 registry 容器跑起来就再也不管了结果几个月后发现磁盘被镜像堆满或者因为误删容器导致数据丢失。我现在固定做四件事用 compose 管理、挂载数据目录、开启自动重启、定期检查磁盘。这四件事做好基本不会出大问题。第二insecure-registries 是一个双刃剑。它省去了配证书的麻烦但也意味着镜像流量在网络上以明文传输。如果是纯内网环境我能接受如果仓库要暴露在公网或者跨安全域传输强烈建议配 HTTPS。配置思路是用 Nginx 或者其他反向代理把 443 端口的 TLS 流量转发到 registry 的 5000 端口同时给 registry 设置证书路径的环境变量。这样客户端的 daemon.json 也可以去掉 insecure-registries改用证书验证。第三能自动化的操作尽量自动化。如果你经常往私有仓库推送镜像建议把打标签加推送这件事写成脚本或者直接集成到 CI 流水线里。自动化之后团队成员不用记住一堆命令也减少了误操作的概率整体效率提升非常明显。我后来把推送脚本放到项目仓库里新同事入职第一天照着脚本跑一遍就能把环境准备好省了我不少答疑时间。
返回列表