ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Docker Hub 镜像拉不下来怎么办?基于 DockerHub 镜像集中营讲透 registry-mirrors、镜像代理与排错

Docker Hub 镜像拉不下来怎么办?基于 DockerHub 镜像集中营讲透 registry-mirrors、镜像代理与排错 如果你最近在国内使用 Docker大概率遇到过这些情况dockerpull nginx然后一直Waiting或者TLS handshake timeout甚至context deadline exceeded还有一种更折磨人的有时候能拉 有时候完全拉不下来。这时候很多教程都会告诉你换个 Docker 镜像源。然后给你一份{registry-mirrors:[https://xxx.xxx.com]}复制进去。能用最好。不能用的时候很多人就不知道到底是哪一层出了问题。最近我整理了一个开源仓库DockerHub 镜像集中营项目地址https://github.com/Rodert/DockerHub同时还有 GitHub Pageshttps://rodert.github.io/DockerHub/这个项目目前主要做两件事情Docker 官方工具下载入口整理 Docker Hub 公共镜像地址整理并分别给出了Windows macOS Linux Linux Rootless几种环境的配置方法。今天不只是给大家抄几个地址。我们顺便把一个问题彻底讲清楚Docker 镜像代理到底是怎么工作的以及registry-mirrors和docker pull 镜像代理地址/xxx到底有什么区别。一、Docker pull 到底在拉什么比如我们执行dockerpull nginx:latest表面上只有nginx:latest实际上 Docker 会把它解释成类似docker.io/library/nginx:latest这里面包含几层信息docker.ioRegistry。libraryNamespace。nginxRepository。latestTag。所以完整理解docker.io/library/nginx:latest二、为什么 nginx 前面有 library很多刚学 Docker 的同学都会疑惑。自己平时明明写dockerpull nginx为什么通过一些镜像代理时却要写dockerpull docker.1ms.run/library/nginx:latest原因就在library这是 Docker Hub 官方镜像使用的 Namespace。比如nginx redis mysql ubuntu node golang这些官方镜像完整路径可以理解成library/nginx library/redis library/mysql library/ubuntuDocker CLI 平时帮我们省略掉了library/所以dockerpull nginx实际上相当于docker.io/library/nginx三、个人镜像就不一样比如username/myapp就不能写成library/myapp因为它真正的 Namespace 是username完整结构docker.io/username/myapp:latest所以通过代理地址时dockerpull proxy.example.com/username/myapp:latest而不是dockerpull proxy.example.com/library/myapp这个细节非常容易踩坑。四、Docker Hub 国内访问慢问题在哪执行dockerpull nginxDocker 并不是直接下载一个nginx.tar就结束。背后大概会经历Docker CLI ↓ Docker Daemon ↓ Docker Registry ↓ 获取 Manifest ↓ 解析镜像 Layer ↓ 逐层下载 ↓ 校验 Digest ↓ 解压 ↓ 写入本地镜像存储一张 Docker 镜像不是一个大文件而通常由多个 Layer组成。例如nginx ├── Layer A ├── Layer B ├── Layer C └── Config只要其中某一层连接超时整个docker pull体验就会很差。五、这就是镜像代理存在的意义原本你的服务器 ↓ Docker Hub如果链路不稳定可以变成你的服务器 ↓ 国内/可访问代理 ↓ Docker Hub代理服务器负责获取 Manifest 获取 Layer 缓存 Layer 转发 Registry 请求用户连接的首先变成镜像代理节点。六、项目目前整理了哪些公共地址仓库当前收录了一批无需付费 无需 Token 公开可使用的 Docker Hub 镜像服务。例如docker.1panel.livedocker.1ms.rundockerproxy.netdockerproxy.linkdocker.m.daocloud.iodocker.jiaxin.site需要强调一点公共镜像地址并不是永久基础设施。它们可能因为流量压力 服务器维护 网络变化 限速 运营策略随时发生变化。所以这个仓库的意义其实不是找到一个永远不失效的神仙地址。而是持续维护一份可以快速切换的候选列表。七、镜像地址有两种常见使用方式这是整篇最重要的一部分。第一种直接修改 docker pull 地址。例如原来dockerpull nginx:latest改成dockerpull docker.1ms.run/library/nginx:latest或者dockerpull dockerproxy.net/library/nginx:latest整个路径变成Docker CLI ↓ dockerproxy.net ↓ library/nginx八、这种方式最大的优点是什么非常简单。不用修改 Docker 配置。比如你只是临时下载一次镜像。直接dockerpull docker.1ms.run/library/nginx:latest即可。特别适合排查问题。因为如果dockerpull nginx失败但是dockerpull docker.1ms.run/library/nginx成功基本就可以确定Docker 本身没问题主要是 Docker Hub 链路有问题。九、但是直接代理地址有一个缺点最终镜像名字可能带代理地址。例如dockerimages看到docker.1ms.run/library/nginx而不是nginx某些 Docker Compose 文件services:nginx:image:nginx:latest仍然引用nginx这时候可能不如registry-mirrors方便。十、第二种方式registry-mirrors项目里推荐的另一种方式修改 Docker Daemon 配置。Linux/etc/docker/daemon.json例如{registry-mirrors:[https://docker.1ms.run,https://dockerproxy.net,https://dockerproxy.link]}然后sudosystemctl daemon-reloadsudosystemctl restartdocker以后还是dockerpull nginx不需要改成dockerpull dockerproxy.net/...Docker Daemon 会根据 Registry Mirror 配置尝试通过镜像服务获取。十一、两种方法可以这样理解方法一改镜像名docker pull ↓ 代理 Registry ↓ Docker Hub命令直接指定dockerpull dockerproxy.net/library/nginx方法二配置 registry-mirrors用户依旧dockerpull nginx然后Docker Daemon ↓ Registry Mirror ↓ Docker Hub代理过程对上层更透明。十二、哪种方法更适合小白我通常建议排错时先直接 pull。例如dockerpull docker.1ms.run/library/nginx:latest如果成功说明代理本身可用。再考虑写进registry-mirrors。而不是一开始daemon.json 改五遍 Docker 重启五遍最后还不知道到底哪有问题。十三、Linux 正确配置方式创建目录sudomkdir-p/etc/docker编辑sudonano/etc/docker/daemon.json或者sudovim/etc/docker/daemon.json写{registry-mirrors:[https://docker.1ms.run,https://dockerproxy.net,https://dockerproxy.link]}保存。重新加载sudosystemctl daemon-reload然后sudosystemctl restartdocker十四、第一件事不要急着 pull先检查 Docker 有没有正常启动sudosystemctl statusdocker如果active (running)再继续。十五、检查 Mirror 是否真的读取到了执行dockerinfo找到Registry Mirrors:理想情况下应该看到https://docker.1ms.run/ https://dockerproxy.net/ https://dockerproxy.link/如果完全没有说明 Docker 没读到你的配置。这时候一直dockerpull意义不大。十六、然后再测试最简单的镜像我建议dockerpull hello-world或者dockerpull nginx:latest而不是一上来dockerpull 一个几 GB 的 AI 镜像测试镜像越简单变量越少。十七、daemon.json 最常见的问题JSON 写错例如{registry-mirrors:[https://docker.1ms.run,]}注意最后多了一个逗号。JSON 不允许trailing comma。Docker 可能直接启动失败。十八、怎么检查 JSON如果机器安装jq可以jq./etc/docker/daemon.json正确会格式化输出。错误会直接parse error。这个方法非常实用。十九、Docker 重启失败怎么办执行sudosystemctl statusdocker还不够继续sudojournalctl\-udocker\--no-pager\-n50经常可以看到invalid characterunable to configureinvalid mirror等真正错误。所以不要看到 Docker 起不来就重装。先看日志。二十、一个比较完整的 Linux 排错流程可以直接保存dockerversion第一步dockerinfo第二步cat/etc/docker/daemon.json第三步jq./etc/docker/daemon.json第四步sudosystemctl restartdocker第五步sudosystemctl statusdocker第六步dockerpull nginx:latest如果失败再dockerpull\docker.1ms.run/library/nginx:latest这样很容易定位Docker 问题 配置问题 Mirror 问题 网络问题到底是哪一个。二十一、Docker Desktop 怎么配置Windows 和 macOS 使用Docker Desktop时一般不建议直接找系统里的daemon.json因为 Docker Desktop 底层实际上运行Linux VM或者对应虚拟化环境。直接Docker Desktop ↓ Settings ↓ Docker Engine修改 JSON。二十二、比如原来的配置是{builder:{gc:{defaultKeepStorage:20GB,enabled:true}},experimental:false}千万不要直接全部删掉改成{registry-mirrors:[https://docker.1ms.run]}更合理的是合并字段。例如{builder:{gc:{defaultKeepStorage:20GB,enabled:true}},experimental:false,registry-mirrors:[https://docker.1ms.run,https://dockerproxy.net]}二十三、然后点击Apply Restart等待 Docker Desktop 重启。重新打开 Terminaldockerinfo检查Registry Mirrors然后dockerpull nginx二十四、Windows Server 又不一样如果你使用Windows Server Docker Engine而不是 Docker Desktop配置可能在C:\ProgramData\docker\config\daemon.json修改以后Restart-Servicedocker再docker info这和普通 Windows Docker Desktop不是一回事。很多教程把二者混在一起导致用户找半天配置文件。二十五、Rootless Docker 又是一个坑如果使用Rootless Docker它通常不会读取/etc/docker/daemon.json而是当前用户~/.config/docker/daemon.json创建mkdir-p~/.config/docker编辑vim~/.config/docker/daemon.json写{registry-mirrors:[https://docker.1ms.run]}然后systemctl--userrestartdocker注意不是 sudo systemctl restart docker而是systemctl --user二十六、为什么有时候配置了多个 Mirror 还是失败因为registry-mirrors不是高可用 CDN 魔法。不同镜像服务可能缓存策略不同 支持镜像范围不同 限流不同 访问链路不同 同步状态不同某一个地址今天可以不代表明天永远可以。所以仓库才会持续维护多个候选地址。二十七、不要一次配置十几个地址看起来越多越稳。实际未必。如果里面大部分已经失效。反而可能增加排错复杂度。我更建议23 个已验证可用地址足够。定期测试。失效就替换。二十八、怎么快速测试一个镜像代理最简单dockerpull\dockerproxy.net/library/hello-world:latest或者dockerpull\docker.1ms.run/library/nginx:latest如果连hello-world都无法获取基本没必要继续拿它拉大镜像。二十九、也可以先测试 HTTP例如curl-Ihttps://dockerproxy.net但是要注意HTTP 首页能打开不代表 Docker Registry API 一定正常。因为 Docker 拉镜像涉及Registry API Manifest Blob 鉴权 Redirect多个流程。所以最终还是dockerpull最有代表性。三十、Docker 镜像拉取常见错误怎么判断1. context deadline exceeded例如net/http: request canceled while waiting for connection常见方向网络链路 DNS 代理 Registry 不可访问2. TLS handshake timeout说明连接建立阶段就卡住。优先看网络 代理 证书 Registry 可达性3. manifest unknown这和网络不一定有关。例如dockerpull nginx:not-exist-tag就会manifest unknown。因为这个 Tag不存在。三十一、再比如 unauthorized如果private image没有登录dockerpull username/private-image可能unauthorized这时候换10 个镜像源也没用。因为真正问题权限。应该dockerlogin而不是疯狂换代理。三十二、429 又是什么例如Too Many Requests说明限流。可能Docker Hub 限流也可能公共代理服务限流。公共镜像毕竟不是无限带宽。这也是为什么公共服务应该合理使用。三十三、镜像下载到一半失败怎么办Docker 的 Layer 是内容寻址。很多已经下载并校验完成的 Layer不一定需要全部重新来一遍。再次dockerpull nginxDocker 会根据本地 Layer 状态继续处理。这也是分层镜像Layer Cache的优势之一。三十四、为什么很多镜像看起来下载好多份例如Downloading Extracting Pull complete因为Docker Image不是单文件。可以理解Image ├── Base OS Layer ├── Runtime Layer ├── Dependency Layer └── Application Layer每一层都有自己的 Digest。三十五、Digest 是什么比如sha256:xxxxxxx本质上内容哈希。它帮助 Docker校验 Layer 识别重复 Layer 复用缓存 保证内容一致性所以镜像代理并不是偷偷重新制作一个 nginx。正常情况下它代理的是同一套 Registry 内容。最终还要通过Digest进行内容校验。三十六、所以镜像代理和“重新打包镜像”不是一回事这两件事情一定要区分。镜像代理Docker Hub ↓ Proxy ↓ Client主要改变下载路径。重新打包原镜像 ↓ 重新 build / 修改 ↓ 另一张镜像这时候Digest都会变化。安全语义完全不同。三十七、企业环境最好怎么做如果只是个人开发 小团队公共镜像代理非常方便。但是企业生产环境更建议Docker Hub ↓ 企业 Registry / Pull-through Cache ↓ 内部服务器例如Harbor Registry Cache Nexus Artifactory之类架构。这样可以内部缓存 权限控制 审计 镜像扫描 稳定性控制不依赖随机公共节点。三十八、为什么企业更应该缓存假设有100 台服务器全部拉node:22如果每台都去公网重新拉。浪费带宽 时间 外部请求额度。有内部 Cache第一次 ↓ Docker Hub ↓ 内部 Registry 后面 99 台 ↓ 内部 Registry效率会高很多。三十九、Docker Compose 同样会受到镜像问题影响比如services:mysql:image:mysql:8.4redis:image:redis:7nginx:image:nginx:latest执行dockercompose up-d实际第一步检查本地镜像。如果没有自动 pull。所以你看到Docker Compose 一直卡住很可能不是Compose 本身有 Bug。而是背后的 image pull 卡住。四十、怎么验证先单独dockerpull mysql:8.4dockerpull redis:7dockerpull nginx:latest全部成功以后dockercompose up-d如果这时候秒起说明之前卡的是镜像下载。这是很实用的排错技巧。四十一、CI/CD 也是一样例如 GitHub Actions、Jenkins、自建 RunnerBuild ↓ docker pull ↓ docker build ↓ docker push如果基础镜像FROM golang:1.25拉不到后面Go 编译根本都没开始。日志却可能看起来Docker Build 失败。实际上是Base Image Retrieval 失败。四十二、所以排 Docker Build 也要先看 FROM例如FROM node:22-alpine先测试dockerpull node:22-alpine如果这一步都失败那RUN npm install根本不是重点。这个思路能省大量时间。四十三、项目本身为什么用了 GitHub Pages这个仓库并不是一个复杂后端项目。结构目前非常简单DockerHub ├── README.md ├── index.html ├── assets └── .github/workflows/pages.yml也就是说它本质上是一份持续维护的资源索引。四十四、index.html 做什么项目同时准备index.html用于把Docker 工具下载 镜像地址 配置示例直接做成一个简单网页。用户不一定打开 GitHub 看 README。也可以访问GitHub Pages。四十五、Pages 又是怎么自动发布的仓库里的.github/workflows/pages.yml使用 GitHub Actions。主要流程push main ↓ Checkout ↓ Configure Pages ↓ Upload Artifact ↓ Deploy Pages例如on:push:branches:-main意味着main 分支更新后自动触发网页部署。四十六、这个思路其实非常适合做“持续维护型资料库”比如Docker 镜像 AI API 地址 开源软件下载 开发者工具导航 模型价格这些资料有一个共同特点经常变化。如果只是发一篇博客2026 年写完 ↓ 半年以后大量失效。而 GitHub 仓库持续 Commit ↓ README 更新 ↓ Pages 自动上线更适合。四十七、这也是这个项目真正值得借鉴的地方它不是代码越复杂越好。而是解决一个高频实际问题 持续更新数据 给用户一个固定入口比如用户以后只需要记https://github.com/Rodert/DockerHub镜像地址变化仓库维护者更新。用户不用重新搜索几十篇过期博客。四十八、如果让我继续升级这个项目我会加什么第一镜像状态自动检测。例如维护mirrors:-name:1msurl:https://docker.1ms.run-name:dockerproxyurl:https://dockerproxy.netGitHub Action每 6 小时 ↓ 测试 ↓ 更新 Status最终网页 可用 较慢 不可用这个价值会非常大。四十九、甚至可以写一个检测脚本例如 Bash#!/bin/bashMIRRORS(docker.1ms.rundockerproxy.netdockerproxy.link)formirrorin${MIRRORS[]};doechoTesting$mirrorstart$(date%s%3N)iftimeout20\dockermanifest inspect\$mirror/library/nginx:latest\/dev/null21thenend$(date%s%3N)echoOK$((end-start))mselseechoFAILEDfidone定时运行。五十、为什么用 manifest inspect 测试很合适因为dockerpull真的会下载全部 Layer。如果每隔 10 分钟拉几十个镜像成本太高。而dockermanifest inspect主要验证Registry Manifest 基础访问链路更适合健康检查。五十一、再进一步可以生成 mirrors.json例如[{name:1ms,url:docker.1ms.run,status:online,latency:236},{name:dockerproxy,url:dockerproxy.net,status:online,latency:410}]前端fetch(./mirrors.json)自动显示。这样项目就从静态地址列表升级成Docker Mirror Status Page。五十二、甚至可以按地区测试比如北京 上海 广州 香港 新加坡分别跑检测节点。得到Mirror A 北京 120ms 上海 90ms 广州 Timeout用户就能根据网络地区选择。这会比单纯给 10 个地址更有价值。五十三、还可以自动生成 daemon.json用户在网页选择☑ 1ms ☑ dockerproxy页面自动生成{registry-mirrors:[https://docker.1ms.run,https://dockerproxy.net]}点击复制。然后分别提供Linux Windows macOS Rootless操作说明。项目体验会非常完整。五十四、Docker 镜像问题最终应该这样排查如果dockerpull nginx失败不要马上重装 Docker。按照下面顺序1. docker versionDocker 是否正常。2. docker infoDaemon 是否正常Mirror 是否生效。3. 直接访问代理地址拉取确认代理是否可用。4. 检查 daemon.json确认 JSON 正确。5. 查看 Docker 日志确定是否配置问题。6. 判断错误类型是网络、Tag、权限还是限流。五十五、一个最终可保存的 Linux 配置例如{registry-mirrors:[https://docker.1ms.run,https://dockerproxy.net,https://dockerproxy.link]}保存/etc/docker/daemon.json然后sudosystemctl daemon-reloadsudosystemctl restartdocker检查dockerinfo测试dockerpull nginx:latest如果还失败直接dockerpull\docker.1ms.run/library/nginx:latest做对比。这个流程基本能解决大部分“小白只知道 Docker 拉不下来但不知道问题在哪”的情况。总结Rodert/DockerHub这个项目本身并不复杂。它解决的是一个特别实际的问题Docker Hub 地址不稳定时到哪里快速找到仍可测试的公共镜像并且知道 Windows、macOS、Linux 应该怎么配置。但如果只是收藏几个镜像地址其实还不够。真正值得理解的是背后的关系docker pull nginx ↓ docker.io/library/nginx ↓ Registry ↓ Manifest ↓ Layers ↓ 本地 Docker Image加入 Mirror 后Docker Client ↓ Docker Daemon ↓ Registry Mirror ↓ Docker Hub ↓ Manifest / Layers ↓ 本地缓存而registry-mirrors解决的是让 Docker Daemon 在不改变日常镜像名的情况下通过镜像服务获取 Docker Hub 内容。如果只是临时排错dockerpull\docker.1ms.run/library/nginx更直接。如果希望Docker Compose Docker Build 日常 docker pull都自动使用 Mirror再配置registry-mirrors。这两种使用场景不要混淆。最后还要记住公共镜像地址本质上是外部服务不可能保证永远有效。所以相比收藏一篇两年前的 Docker 镜像教程更有价值的做法是维护一个持续更新、可以共同反馈、最好还能自动检测可用性的镜像资源库。这也是这个开源项目后面最值得继续发展的方向。项目地址https://github.com/Rodert/DockerHubGitHub Pageshttps://rodert.github.io/DockerHub/
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进