Docker Hub 超时,先别急着换节点

执行 docker pull 时出现 i/o timeoutcontext deadline exceededTLS handshake timeout 或「无法连接 Docker Hub」,很多人第一反应是把 Clash 切到全局模式,或者反复更换节点。这样有时能暂时恢复,但并不能解释问题,也容易把宿主机浏览器、局域网服务和容器内网流量一起送进代理,最后变成下载能用、内网访问反而异常。

更准确的思路是先确认真正发起连接的进程。浏览器访问 Docker Hub 时,流量由浏览器产生;执行 docker pull 时,通常是宿主机上的 Docker daemon 访问 registry-1.docker.io,随后还可能访问认证服务、镜像仓库和 CDN。若使用 Docker Desktop,daemon 可能运行在一个 Linux 虚拟机或内部服务环境中;若使用 Linux Docker Engine,则一般是 dockerd 直接从宿主机网络栈出站。两者都不等同于当前终端窗口,也不会因为 Clash 已经开启「系统代理」就自动继承代理。

Docker Hub 的一次拉取可能包含多个阶段:先解析域名,再访问 Registry API,获取 Bearer Token,读取镜像 manifest,最后并发下载多个 layer。只要其中一个阶段没有经过正确的代理,最终就可能表现为整个 docker pull 失败。因此,排障时不要只测试首页能否打开,而要观察 Clash 连接日志里是否出现真实目标域名,以及这些连接是否命中了预期策略组。

先记住一个边界:系统代理主要影响遵循系统设置的应用,Docker daemon 通常需要单独配置代理;而透明代理或 TUN 才是从网络层接管未设置代理的进程。两者可以配合使用,但不要把它们当成同一个开关。

Docker Hub 拉取链路与 Clash 的职责

在规则模式下,Clash Mihomo 会根据域名、IP、进程或规则集决定流量走代理还是直连。对于 Docker Hub,至少应关注三类目标。第一类是 Docker Registry,例如 registry-1.docker.io;第二类是认证服务,例如 auth.docker.io;第三类是镜像 layer 实际所在的存储或 CDN 域名。不同镜像、不同时间和不同区域可能使用不同的下载地址,所以只添加一个 Docker Hub 域名并不一定足够。

还要区分宿主机访问容器访问。宿主机执行 docker pull 时,主要排查 daemon 的出站路径;容器执行 apt updatenpm install 或应用启动后访问外部 API 时,排查的是容器网络、容器 DNS、默认路由以及透明代理是否能看到这部分连接。前者修好,并不代表后者自动修好。

如果 Clash 运行在宿主机上,Docker daemon 访问宿主机代理端口时还涉及监听地址。许多客户端默认只监听 127.0.0.1,宿主机本身可以访问,但 Docker 虚拟网络或其他设备无法访问。此时即使把 HTTP_PROXY 写成宿主机地址,也会得到连接拒绝。透明代理方案通常要让 Mihomo 监听可达地址,同时配合防火墙限制来源,不能简单地把代理端口暴露到整个公网。

Mihomo 透明代理基础配置

对于希望让 Docker daemon、容器内进程和不支持代理变量的程序统一出站的场景,可以在 Clash Mihomo 中启用 TUN。TUN 会创建虚拟网络接口,由内核接收符合条件的流量,再交给 Mihomo 按规则处理。下面是一份用于理解参数关系的基础片段,实际端口、DNS 地址和网卡名称应根据客户端版本调整。

mixed-port: 7890
allow-lan: true
mode: rule

tun:
  enable: true
  stack: mixed
  auto-route: true
  auto-detect-interface: true
  strict-route: true
  dns-hijack:
    - any:53

dns:
  enable: true
  listen: 0.0.0.0:1053
  enhanced-mode: fake-ip
  fake-ip-range: 198.18.0.1/16
  nameserver:
    - https://dns.cloudflare.com/dns-query
    - https://dns.google/dns-query

auto-route 负责尝试写入路由,让系统流量进入 TUN;auto-detect-interface 用于自动识别当前出口网卡,笔记本在 Wi-Fi、有线网卡和 VPN 之间切换时尤其重要;strict-route 可以减少部分流量绕过 TUN 的情况,但在复杂虚拟网络环境中也可能造成宿主机或 Docker 网段访问异常。因此,开启后应重点测试宿主机网关、Docker bridge 网段、公司内网和本地 DNS,而不是只看网页。

如果客户端提供「增强模式」「TUN 模式」或「服务模式」开关,通常还需要安装内核服务并授予管理员权限。Windows、macOS 与 Linux 的权限提示不同,按钮名称也可能随 Clash Verge Rev、Mihomo Party 或其他前端变化。判断是否生效,不要只看界面上的绿色状态,应使用 ip routeroute print 或客户端连接日志确认流量确实被接管。

不要一开始就把所有流量强制代理:Docker 的 bridge、宿主机回环地址、局域网网关和企业内网域名通常需要直连。建议先保留回滚配置,确认 Docker Hub 可用后,再逐步扩大接管范围。

规则分流与 Docker daemon 代理配置

规则方面,建议为 Docker Hub 单独建立策略组,而不是把它混入泛用的「国外网站」组。这样可以在连接日志中直接看到 Docker 拉取使用了哪个出口,也能避免自动测速组频繁切换节点。示例规则可以作为起点:

proxy-groups:
  - name: Docker
    type: select
    proxies:
      - 自动选择
      - 节点 A
      - DIRECT

rules:
  - DOMAIN,registry-1.docker.io,Docker
  - DOMAIN,auth.docker.io,Docker
  - DOMAIN-SUFFIX,docker.io,Docker
  - DOMAIN-SUFFIX,docker.com,Docker
  - MATCH,DIRECT

上面的规则并不能覆盖所有 layer CDN,因此第一次失败时应查看 Clash 的连接记录,记录实际出现的主机名,再按照最小范围补充规则。若某个下载域名被宽泛的国内直连规则提前匹配,后面的 Docker 规则不会生效;规则顺序必须把 Docker 专用规则放在通用规则之前。对于生产环境,还应避免使用不明来源的「一键 Docker 加速规则」,因为过宽的域名或 IP 列表可能把无关业务流量也送入代理。

如果你不想依赖 TUN,也可以直接为 Docker daemon 配置 HTTP 或 HTTPS 代理。在 Linux 上,可通过 systemd drop-in 为 docker.service 设置环境变量:

sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf <<'EOF'
[Service]
Environment="HTTP_PROXY=http://127.0.0.1:7890"
Environment="HTTPS_PROXY=http://127.0.0.1:7890"
Environment="NO_PROXY=localhost,127.0.0.1,::1,.local,192.168.0.0/16,172.16.0.0/12,10.0.0.0/8"
EOF

sudo systemctl daemon-reload
sudo systemctl restart docker
systemctl show --property=Environment docker

这里的端口必须是 Clash 的 HTTP 或混合端口,而不是只支持 SOCKS 的端口。NO_PROXY 则用于排除本机、局域网和 Docker 内部网段,具体网段要按你的网络规划修改。修改后先确认 daemon 已读取新环境,再执行 docker pull;否则服务仍可能使用旧配置,导致你误以为规则没有生效。

Docker Desktop 用户需要在 Docker Desktop 的代理设置中配置 daemon 代理,不能只修改宿主机 shell 的 HTTP_PROXY。如果同时启用了 Docker Desktop 内置代理、Clash TUN 和系统代理,建议暂时只保留一种主路径做验证,确认链路后再组合,否则日志会出现多层转发,问题很难定位。

DNS、日志与生产环境排障顺序

DNS 是 Docker Hub 超时中最容易被忽略的一环。宿主机能够解析域名,不代表 Docker daemon 使用相同的解析器;容器中的 /etc/resolv.conf 也可能指向 Docker 内置 DNS 转发地址。若解析结果受到污染、返回不可达 IP,TCP 连接自然会超时。使用 fake-IP 时,要确认 Docker 与内网域名没有被错误映射;如果公司内网依赖特定 DNS,应该通过 nameserver-policy 或直连规则保留内网解析路径。

建议按以下顺序操作,每一步只改变一个变量:

  1. 确认服务状态:检查 Clash Mihomo 内核、TUN 或代理端口是否正在运行,并记录混合端口监听地址。
  2. 确认 daemon 路径:在 Linux 查看 Docker 服务环境变量;在 Docker Desktop 查看应用内代理设置,不要只检查当前终端的 env 输出。
  3. 测试解析:分别在宿主机、Docker daemon 所在环境和临时容器中查询 registry-1.docker.io,比较返回结果与解析耗时。
  4. 观察真实连接:执行 docker pull alpine:latest 或一个体积较小的公开镜像,同时在 Clash 连接列表中确认 Registry、认证服务和 layer 域名的策略。
  5. 固定节点复测:暂时不要使用会自动切换出口的策略组,连续拉取同一镜像,判断故障是规则问题、节点问题还是并发下载问题。
  6. 最后再调参数:只有确认链路正确后,才考虑 MTU、fake-IP 排除、TCP 并发、Docker DNS 或代理超时等高级参数。

日志中的现象可以帮助缩小范围。若完全看不到 registry-1.docker.io,通常说明 daemon 没有经过 Clash,或者 DNS 解析和连接在代理外完成;若能看到 Registry 但没有认证域名,可能是认证请求被直连规则截断;若 manifest 成功而 layer 下载失败,则重点检查实际 CDN 主机名、节点带宽和并发连接稳定性。若返回 401 Unauthorized,不一定是代理故障,也可能是私有仓库凭据、镜像名称或登录状态问题。

生产环境还要考虑安全和可维护性。代理端口不要监听公网,Docker daemon 的代理配置不要把带密码的 URL 写进所有用户都能读取的脚本;涉及企业镜像时,优先使用内部 Registry、镜像缓存或 Pull Through Cache,减少每台机器直接访问公共仓库的次数。对于 CI runner,应固定出口和 DNS,记录镜像摘要、失败时间、目标域名与代理策略,避免只保存一句「Docker Hub 超时」。如果网络策略允许,内部缓存通常比让大量构建任务共享一条桌面 Clash 节点更稳定。

还要注意 MTU 与 IPv6。TUN、Docker bridge、虚拟机和 VPN 叠加后,路径上的有效 MTU 可能下降,表现为小请求成功、大 layer 下载卡住或 TLS 长时间无响应。可以先在测试环境降低虚拟接口或 Docker 网络的 MTU 做对照,但不要未经验证就在生产机全局修改。IPv6 也应单独测试:如果系统优先解析 AAAA 记录,而代理链路实际只支持 IPv4,可能出现浏览器偶尔成功、daemon 经常超时的差异。

相比一些只提供固定镜像加速地址的同类方案,单纯改 Docker daemon 的 registry mirror 往往无法覆盖认证跳转、临时 CDN 和容器运行时的其他外联请求,文档也常常没有解释 DNS、TUN 与 Docker Desktop 之间的边界;Clash 官网 的价值在于把 Mihomo 透明代理、规则命中、daemon 独立代理、DNS 分流和日志验证放进同一套可回滚的排障路径,既能按需选择 TUN,也能在不接管整台机器的前提下精确处理 Docker Hub 流量。如果你正准备配置这套环境,不妨下载 Clash 官网,结合本文的规则与检查顺序逐项验证,通常比盲目切全局或反复更换节点更容易找到真正的超时原因。