Docker镜像源配置:解决docker pull慢与daemon.json坑 开头前两天有个朋友在群里哀嚎docker pull mysql:8.0卡了半天进度条像蜗牛一样等了几分钟直接报EOF超时。这事太常见了。原因不复杂——Docker 默认走的是 Docker Hub 的官方源服务器在境外国内网络环境访问那叫一个不稳定。尤其当你需要拉取或部署 MySQL、Redis、Nginx 这些常用镜像又赶上刚装完 Docker Desktop 或者新装完 Ubuntu 系统时第一反应基本都是这软件是不是坏了。其实问题出在镜像源上。这篇内容就是来解决这个事的。我会从镜像源的工作原理讲起然后按 Linux、Windows/macOS 等不同环境分别给出配置方法再聊聊配置完以后最常见的错误和排查方式最后结合我实际用过的一些国内镜像源做对比并分享几个调优技巧。无论你是刚接触 Docker 的新手还是已经被daemon.json折腾过的老手这篇文章应该都能给你一些参考。1. Docker镜像拉取慢的根源先把加速这回事讲清楚很多人以为 Docker 镜像源就是改一个下载地址那么简单先别急着复制粘贴理解原理能帮你少踩很多坑。1.1 默认的Docker Hub为什么慢Docker 客户端在拉取镜像时默认去https://registry-1.docker.io请求镜像层数据。这个服务部署在全球各地但国内直连的延迟和丢包率都偏高。平时拉一个小镜像可能还能忍遇到mysql:8.0、postgres:15这种动辄几百 MB 甚至上 GB 的镜像数据要经过海底光缆、国际出口、运营商骨干网中间任何一个环节出现拥堵或丢包就会有明显的卡顿甚至直接中断。这里有个容易被忽略的细节Docker 拉取镜像并不是一次性下载一个大文件而是分成多层layer每一层独立校验和下载。如果某一层因网络抖动失败Docker 会尝试重试重试次数耗尽后就报错。所以你会看到有时候进度条跑到 80% 又从头开始这是因为某层失败后整个流程被打断不是 Docker 傻了是网络确实不稳定。1.2 国内镜像源到底是什么国内镜像源本质上是一个托管在境内服务器上的 Docker Registry 镜像仓库。它会把 Docker Hub 上的热门镜像同步到自己的服务器或者作为缓存代理转发你的拉取请求。你配置了国内源以后拉取请求不再直接打到docker.io而是先指向这些国内站点速度和稳定性就好很多。这就好比你想买一个进口水果跨境直邮又慢又可能被扣住干脆在国内的经销商仓库里囤一批现货你直接去仓库提货自然快得多。当然这个经销商仓库也不是什么水果都囤所以国内镜像源对热门镜像支持很好但对一些冷门镜像可能缓存没有命中这时它会回源到 Docker Hub 去拉取如果回源链路本身也不稳定你可能会发现某些镜像配置了国内源仍然很慢。1.3 常见误区镜像源不是越多越好registry-mirrors这个配置项是数组可以填多个地址。很多人就顺手把网上能找到的源全写进去以为每个源都能分摊压力。实际运行中 Docker 的行为是按数组顺序尝试第一个源失败后换下一个而不是负载均衡似的并发拉取。我见过有人把阿里云、网易、中科大、腾讯云、DaoCloud 全写进去结果第一个源因为某个镜像没有缓存导致回源超时等了好几分钟才轮到下一个源。所以配置的原则是优先放一到两个你验证过比较稳定的源不要把顺序弄乱。后面我会单独用一节讲选型。2. Linux环境下配置registry-mirrors核心操作与权限坑Linux 上配置 Docker 镜像源是最常见的需求也是配置出错率最高的一块。因为会牵扯到 Docker 服务重启、权限、配置文件路径等问题。2.1 确定你的Docker配置文件路径绝大多数情况下路径是/etc/docker/daemon.json。如果这个文件不存在就手动创建。但是如果你用的是 rootless 模式的 Docker那路径会跑到~/.config/docker/daemon.json。还有极少数发行版把 Docker 的配置目录放在/etc/sysconfig/docker这种是特殊情况以系统文档为准。先执行这个命令确认当前 Docker 是否正常工作docker version拿到信息后检查一下 daemon.json 是否已经存在并且是否为空或只有{}cat /etc/docker/daemon.json如果文件不存在或者为空直接写下面的配置sudo tee /etc/docker/daemon.json -EOF { registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com, https://docker.mirrors.ustc.edu.cn ] } EOF注意这里的tee用的是sudo因为/etc/docker目录一般只有 root 能写。有些人是先sudo touch创建文件再用 vim 编辑也可以。2.2 重启Docker而不是重载配置很多新手在这里犯的错误是改完 daemon.json 以后执行systemctl restart docker结果 Docker 启动失败。还有人执行systemctl daemon-reload再 restart其实daemon-reload是针对 systemd 服务文件不是针对daemon.json。修改 daemon.json 之后必须重启 docker 守护进程因为它是启动时读取配置的。正确的顺序是sudo systemctl daemon-reload sudo systemctl restart docker如果 Docker 服务本身不是由 systemd 管理比如你是手动dockerd启动的那就直接重启进程。对于 rootless 模式用systemctl --user restart docker重启后查看 Docker 运行状态sudo systemctl status docker sudo docker info重点看docker info输出里Registry Mirrors这一段它会列出当前生效的镜像源地址。如果这里显示的是你配置的那几个说明配置已经生效。2.3 需要特别留意的权限错误搜索热词里有permission denied while trying to connect to the docker api这个和镜像源无关但如果你是在解决镜像源问题时遇到很可能是因为你执行 Docker 命令的用户不在docker用户组里。解决办法很简单sudo usermod -aG docker $USER newgrp docker然后重新登录一次终端。这个错误会在你重启 Docker 之后突然出现因为某些系统在重启 Docker 时重置了 socket 权限不用紧张加组就行。2.4 配置后拉镜像测试验证配置是否有效最好的方式是拉一个比较大的镜像。比如拉 MySQL 8.0docker pull mysql:8.0正常情况下能看到有进度条在走而且速度比较可观。如果十几秒内没有任何动静或者直接报错那就是源有问题需要换源或者排查网络。这里要注意不要拿hello-world这种小镜像测试它只有几 KB随便哪个源都能秒拉测不出效果。测试完成以后记得docker images看一眼确认镜像真的拉下来了。顺便说一下如果之前拉取失败留下了一些悬空镜像可以用docker system prune清理。3. Docker Desktop下配置镜像源Windows和macOS的坑不一样Windows 和 macOS 上用 Docker 的情况比较复杂因为 Docker Desktop 是一个带 GUI 的应用它的配置位置和作用机制与 Linux 上的原生 Docker 有些区别。3.1 Windows11安装Docker Desktop后的配置入口Windows 上安装 Docker Desktop 以后默认后端是 WSL2。这时候你可能会在网上看到两种改法一种是直接在 Docker Desktop 的Settings - Docker Engine里改 JSON 配置另一种是去 WSL 发行版里改/etc/docker/daemon.json文件。这两种方式在早期版本确实都存在但现在我建议你直接在 Docker Desktop 的 Docker Engine 界面里改因为这里改的是 Docker Desktop 管理的主配置界面里还有一个Apply Restart按钮点一下会自动重启引擎非常方便。具体的 JSON 内容是这样的{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com ] }注意这里如果原来是空的默认配置可能有这句registry-mirrors: []直接替换数组内容即可。改完点Apply Restart等待 Docker 引擎重启。实际重启可能需要一两分钟界面会显示一个小鲸鱼图标在转不要反复点。3.2 WSL2里的daemon.json和Desktop配置的区别如果你在 WSL2 的 Ubuntu 里安装了 Docker Engine 而不是只依赖 Docker Desktop那么两个地方都可能有 daemon.json 配置。这时候要记住Docker Desktop 环境下的 Docker 引擎是在 Windows 侧运行的WSL2 里的/etc/docker/daemon.json只对 WSL2 内独立安装的 Docker Engine 生效。如果你在 WSL2 里直接使用docker命令但在 Windows 侧用 Docker Desktop命令会走 Windows 引擎那你就得改 Docker Desktop 的配置改 WSL 里的文件没用。这个区分是很多人踩坑的地方。我的建议是如果打算主要在 WSL2 里工作可以只在 WSL2 里安装原生的 Docker Engine然后配置/etc/docker/daemon.json不使用 Docker Desktop 的引擎。否则就统一用 Docker Desktop 的配置避免两套配置互相干扰。3.3 macOS上的环境变量配置macOS 的 Docker Desktop 也可以用界面改Docker Engine的 JSON方式和 Windows 一样。但如果你是通过 Homebrew 安装的 docker 命令行工具并且连接的是远程 Docker 主机那就需要在你本地的 shell 配置文件如~/.zshrc里设置export DOCKER_HOSTtcp://你的远程主机:2375这种方式不会用到本地的 registry-mirrors因为镜像源配置在远程主机的 daemon.json 中。这是很多人搞混的一点registry-mirrors 是 Docker 守护进程的配置不是客户端的配置。无论你在哪个机器上执行 docker pull真正去拉镜像的是 Docker daemon 所在的机器。所以如果你想给远程 Docker 主机加速就必须在该主机的 daemon.json 里配置镜像源。3.4 Docker Desktop启动失败的误关联搜索热词里有docker desktop failed to start because virtualisation support wasnt detected。这个报错是 Windows 功能里没有开启虚拟化支持或 BIOS 里的 VT-x/AMD-V 没有启用。它和国内镜像源没有任何关系。但有些人在配置完镜像源以后重启 Docker Desktop 时遇到这个问题会误以为是配置写坏了。解决办法很简单打开 Windows 功能勾选虚拟机平台和适用于 Linux 的 Windows 子系统然后进入 BIOS 开启虚拟化或者确保 Windows 的 Hyper-V 功能正常。设置完以后重新启动即可。如果你的报错是其他类型比如failed to start docker application container engine那么可以打开 Docker Desktop 的Troubleshoot页面重置 Docker 引擎。这里要提醒一句不要一遇到启动失败就重装 Docker Desktop按我上面说的排查路径走95% 都能解决。4. 常用国内镜像源对比与选型建议国内镜像源我前前后后用过十几个有一些现在还在维护有一些已经失效或者限制了使用条件。下面把我知道的情况整理出来。镜像源地址特点注意事项阿里云https://你的ID.mirror.aliyuncs.com稳定需要登录获取专属地址个人专享加速器需要注册后获取中科大https://docker.mirrors.ustc.edu.cn老牌高校源同步频繁偶尔访问量大需要保留备选网易http://hub-mirror.c.163.com老牌源速度尚可现在使用的人少了部分人反馈不稳定DaoCloudhttps://docker.m.daocloud.io免注册社区维护支持多镜像目前我主力推荐之一腾讯云https://mirror.ccs.tencentyun.com免费面向腾讯云用户非腾讯云环境也经常可用需要测试Docker Proxy社区https://dockerproxy.com社区维护的代理不支持搜索可做后备4.1 阿里云个人专属加速器最值得花两分钟配置的源阿里云加速器并不面向所有人开放同一个地址而是要求你登录阿里云控制台访问容器镜像服务页面地址里携带一个专属 ID。每家账号专属的地址形如https://xxxx.mirror.aliyuncs.com。阿里云这个源的特点是同步频率高、节点多、稳定性强。对于大厂上班、有阿里云账号的人来说用这个源是最省心的。配置方式就是拿这个专属地址替换我们前面提到的registry-mirrors数组的第一个位置。阿里云官方文档有提供不同系统的接入代码照着做就行。需要注意的是阿里云加速器目前只支持 Docker Hub 镜像的同步加速其他第三方 registry 如quay.io、ghcr.io是不走这个加速的。4.2 开源公共源中科大和DaoCloud的选择逻辑中科大的docker.mirrors.ustc.edu.cn是我以前一直放在第一位用的但有一段时间访问量很大偶尔出现 503。后来我改成docker.m.daocloud.io作为主力因为它不限制 Docker 版本也不强制登录而且似乎会自动回源拉取 Docker Hub 上的冷门镜像虽然速度不如热门镜像快但起码不是完全不可用。DaoCloud 这个源其实背后是道客DaoCloud公司维护的社区加速器最初是为了解决国内访问 Docker Hub 困难而推出的。目前它还提供多个镜像地址变体不过我一般只使用docker.m.daocloud.io这个。如果你发现这个源拉取某个镜像失败可以临时把dockerproxy.com放到第一位再试。4.3 镜像源失效的识别与响应配置完镜像源以后不要以为一劳永逸了。我遇到过几次这样的情况某天执行docker pull nginx:latest发现进度条一会儿走一会儿停最后报503 Service Unavailable。去docker info里看 Registry Mirrors 还在但就是连不上。这种大概率是镜像源所在服务本身出问题了。建议每隔一两个月用docker info检查一下当前源地址是否仍然有效同时用一个体积适中的镜像比如nginx:alpine实际拉一次。如果发现某个源持续无法使用就把它从registry-mirrors里删掉替换成你备用的源。这里特别提醒不要把不维护的源一直放在队列里因为刚才说过Docker 会按顺序尝试所有源一个失效的源可能会拖慢整个拉取过程。4.4 为什么不建议同时配置过多镜像源网上很多人给的配置示例里列了五六个源看起来好像很保险。但实际使用中源太多会导致两个问题。一是 Docker 拉取某个镜像时按顺序尝试如果第一个源上确实存在该镜像但速度很慢Docker 不会自动切换到第二个源它只有在一个源完全失败或者返回 404 时才会去下一个。所以数组里第一个源的速度决定了大多数情况下你拉镜像的速度。二是有些镜像源之间存在策略差异比如中科大会对某些镜像规范做限制如果多个源交叉回源反而增加不可控因素。我的建议是配置两个源就够一主一备。主源选你实测最稳定的备源在主源失败时顶上。这样既保证了速度也不会因为单一源故障而导致彻底无法拉取。5. 配置后的验证、常见报错与排查链路配置镜像源不是改完 daemon.json 就结束的。实际工作中下面这些报错几乎每个人都有大概率遇到我把排查思路一步步列出来。5.1 docker info里看不到镜像源地址改完配置并重启 Docker 后执行docker info如果你看到Registry Mirrors为空或者压根没有这一项那就说明配置没有生效。第一步检查daemon.json的语法。JSON 格式对逗号和括号非常敏感多一个逗号、少一个引号都会导致整个配置文件无法解析。你自己编辑过文件以后可以用下面的命令快速验证python3 -m json.tool /etc/docker/daemon.json如果能正常输出格式化后的 JSON说明语法没问题如果有报错它会直接告诉你第几行有问题。第二步确认 Docker 守护进程是否真的重启了。如果docker info里还是旧配置说明 Docker 没读到你的新配置或者重启前老的进程还活着。可以ps aux | grep dockerd查看启动参数里有没有--config-file指向其他地方。比如有些发行版用了/etc/docker/daemon.json但 dockerd 启动时另外指定了/etc/docker/daemon.json.bak这种属于安装时自动生成的备份配置需要调整 systemd 服务文件的启动参数。5.2 pull镜像时仍然超时或报EOF配置完镜像源后如果仍然报EOF或net/http: TLS handshake timeout优先检查是否是 Docker 客户端到镜像源服务器的网络问题。先试一下curl -I https://docker.m.daocloud.io/v2/如果这个命令能正常返回 HTTP 状态码说明你的网络到这个镜像源是通的。如果 curl 都卡住或超时那说明你选择的镜像源对你本地网络不友好换个源试试。这里有一个网络环境差异问题你在电信网络下很好用的源到了联通或移动网络下可能就慢了。所以不要迷信网上说的XXX 源最快要自己实测。还有一种特殊情况某些企业内网、代理环境Docker 守护进程需要通过 HTTP 代理访问外网。这种情况下你需要在 daemon.json 里额外配置proxies字段或者在 systemd 中配置 HTTP_PROXY 环境变量。如果你是在公司内网使用 Docker排查镜像源之前先确认代理设置是否正确。5.3 拉取MySQL、Redis等镜像时的失败场景还原以docker pull mysql:8.0为例最常见的失败场景是这样的没有配置镜像源直接从 Docker Hub 拉取进度条一直不动最后报Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: TLS handshake timeout。这时候配置镜像源是立竿见影的。但如果你已经配置了镜像源拉取 MySQL 还是失败还有一个隐藏问题MySQL 8.0 的镜像有几个不同的 tag例如mysql:8.0、mysql:8.0.0、mysql:8.0.32。虽然 Docker Hub 本身支持但某些镜像源可能没有及时同步所有 tag导致你拉取一个冷门的 tag 时回源失败。解决办法是换一个 tag 试试或者用docker pull mysql:lts之类官方推荐的长期支持版。Redis 镜像的失败很多情况下不是镜像源问题而是因为你用了redis:7.0.0这种过时的小版本镜像源里可能没有缓存。多数的场景下redis:latest或者redis:7-alpine就能解决。这个思路适用所有冷门 tag 的拉取。5.4 镜像源失效后的临时解决方案遇到紧急情况比如镜像源提供商挂了你又急着部署服务可以临时切回官方源配置一下也就是把registry-mirrors删掉或者设为空数组然后用docker pull直接拉取。虽然会很慢但至少比完全不可用强。这个办法只适合应急不适合日常使用。另外一个临时方案是使用docker pull时直接指定完整的镜像注册服务器地址。比如某些镜像在quay.io、ghcr.io上你可以在镜像名前面带上域名。如果你需要的镜像在ghcr.io上Docker Hub 上的镜像源是加速不到的必须直连或者使用对应平台的加速服务。这个和国内镜像源是两个维度的问题别搞混。6. 进阶调优让镜像拉取真正飞起来配置好镜像源只是第一步。我在实际部署项目中还做过一些调优让镜像拉取的速度和稳定性进一步提升顺便解决了频繁拉取导致限流的问题。6.1 调整下载并发和重试参数Docker 默认的下载并发数是 3重试次数和超时时间也有默认值。如果你带宽足够可以适当调高并发数。在daemon.json里增加max-concurrent-downloads: 5或更大值。不过这里要慎重并发数太高会让你的 HTTP 连接数激增有的镜像源会认为你是攻击直接限流甚至封 IP。我个人测试下来 5 是一个比较平衡的值。还可以调整max-download-attempts: 5让 Docker 在拉取镜像时多做几次重试。这个参数是近几个版本新加的默认值是 3。如果你网络环境不稳定适当调高到 5 或 6 可以明显减少失败率。注意重试次数过高会导致失败时等待时间变长所以不要无限加大。完整示例配置{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com ], max-concurrent-downloads: 5, max-download-attempts: 5 }6.2 使用docker pull带平台参数的技巧这个技巧很多人不知道。如果你在 x86_64 机器上拉取某些老镜像可能会遇到 tag 同时支持多架构的情况比如mysql:8.0有amd64和arm64版本。默认情况下 Docker 会根据你的系统架构自动选择但如果你是在模拟器环境比如在 Apple Silicon 上跑 x86 容器或者某些特殊网络下Docker 可能会先去查询 manifest 列表这一步也会消耗连接时间。你可以直接用docker pull --platform linux/amd64 mysql:8.0跳过平台探测在明确自己需要的架构时速度更快。6.3 用docker save和load应对镜像源全挂的极端情况国内镜像源虽然多但也可能有集体拉胯的时候。如果你需要部署的环境不止一台机器最稳的方式不是每台机器都去拉镜像而是先在某一台能正常拉取镜像的机器上拉好然后导出为 tar 文件再拷贝到其他机器上导入。docker save mysql:8.0 -o mysql8.tar docker load -i mysql8.tar这个方法是应对镜像源全挂的最终方案也适合离线环境。很多生产系统上不会放 Docker Hub 的访问权限所以提前把镜像保存好才是正经。保存的 tar 文件可能比较大必要时可以 gzip 压缩docker save mysql:8.0 | gzip mysql8.tar.gz不过要注意压缩和解压都会消耗 CPU如果只是内网传输不压缩更快。这个要根据实际场景权衡。6.4 留意Docker版本对镜像源的影响老版本的 Docker比如 19.03 之前对registry-mirrors的支持已经稳定但新版本中 Docker 对镜像源的有效性检查变严格了。如果你还在用很老的发行版系统比如 CentOS 7自带的 Docker 版本可能比较旧有的镜像源已经不兼容比如某些源要求 TLS 1.2 以上老版本 Docker 可能握手失败。这时候升级 Docker 也是解决办法之一。CentOS 7 升级 Docker 的常见命令sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine sudo yum install -y yum-utils device-mapper-persistent-data lvm2 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install docker-ce docker-ce-cli containerd.io成功升级后重启服务再配置镜像源你会发现老系统也能顺利拉取镜像了。7. 个人踩坑小结与建议说了这么多最后分享几个我实践中的经验。第一个经验不要一遇到镜像拉取慢就怀疑是网络问题或者去换源。先运行docker info看看当前生效的 Registry Mirrors 是哪些再用curl测一下源地址的可达性最后才动手改配置。很多时候问题出在配置没有生效而不是源本身。第二个经验daemon.json修改错了是会导致 Docker 服务启动失败的。我有一次在 JSON 里多加了一个逗号systemctl restart docker之后 Docker 直接起不来排查了半天才发现是语法错误。后来我每次修改完都先python3 -m json.tool验证一下再重启就再也没在语法上栽跟头。第三个经验如果你同时用 Docker Desktop 和 WSL2优先统一在 Docker Desktop 的图形界面里配置镜像源。图形界面点 Apply 以后会自动重启引擎避免两边配置冲突。我自己开发时主要用 Docker Desktop但部署到服务器时会在 Linux 上用原生 Docker两边的镜像源配置逻辑一样只是路径不同记熟就不会乱。最后一个建议在国内环境折腾 Docker保持两源一备的心态一个主力镜像源、一个备用镜像源、一个手动离线方案。镜像源加速好用的前提是 Docker Hub 本身可用但假如整个 Docker Hub 或者镜像源服务商出现调整离线镜像包能保证你的服务不会中断。这套组合拳打下来日常拉镜像基本不会再卡进度条了。