Docker pull 超时:先分清是哪一段没有走代理
执行 docker pull 时,很多人会先打开 Clash 的系统代理开关,然后期待 Docker 自动使用代理。实际上,浏览器能访问网页,并不代表 Docker 就能访问 Docker Hub。Docker CLI 只是把请求交给 Docker daemon,真正发起镜像清单、认证和分层下载请求的通常是后台 daemon 进程。它可能运行在宿主机的 systemd 服务里,也可能运行在 Docker Desktop 的虚拟机或 WSL2 环境中,与当前桌面用户的代理设置并不一定处于同一个网络命名空间。
Docker Hub 的一次拉取通常不只访问一个域名。客户端先连接仓库地址,再访问认证服务获取 token,最后从镜像分发节点下载各个 layer。常见域名包括 registry-1.docker.io、auth.docker.io 以及实际承载分层文件的 CDN 域名。因此,只给其中一个域名配置代理,或者只测试 Docker Hub 首页能否打开,都不足以证明 docker pull 的完整链路正常。
排查时先观察错误类型。i/o timeout、context deadline exceeded 通常指向 TCP 连接、路由或代理未接入; lookup ... no such host 更接近 DNS 问题;能够拿到 manifest 但下载 layer 失败,则要继续检查认证域名、CDN、代理规则和 MTU。只有先确认故障发生在 DNS、认证、清单还是分层下载阶段,后面的配置才不会变成反复试错。
Docker daemon 与 Clash 的数据流并不相同
最容易复用、也最稳定的方案是给 Docker daemon 单独配置 HTTP(S) 代理。数据流大致是:
- 终端执行
docker pull,Docker CLI 把请求发送给本地 Docker daemon。 - daemon 根据自己的环境变量或服务配置,把对 Docker Hub 的 HTTPS 请求发往 Clash 的 HTTP 或 mixed 代理端口。
- Clash 接收请求后,按照当前配置中的域名规则选择代理节点或直连出口。
- 认证服务返回 token,daemon 再通过同一代理链路访问 registry 和 layer CDN。
这条链路与“容器内应用走透明代理”是两个问题。给 daemon 设置代理,主要解决 docker pull、docker push 和部分构建阶段的出站请求;它不会自动让容器里的所有 TCP、UDP 流量都经过 Clash。反过来,开启 TUN 或透明代理接管宿主机流量,也不一定能覆盖 Docker Desktop 虚拟机、独立 network namespace 或被 Docker 自己维护的 DNS 转发路径。
不要把宿主机的 127.0.0.1:7890 直接填进普通容器或 Docker daemon 的代理配置里。对容器来说,127.0.0.1 指向容器自身;对 Docker Desktop 来说,daemon 还可能运行在独立的 Linux 虚拟机中。必须先确认 Clash 和 Docker daemon 位于哪一个网络环境。
先给 Docker daemon 配置显式代理
如果目标只是解决宿主机上的 docker pull 超时,优先使用 daemon 代理,不要一开始就改复杂的 iptables 透明转发。Linux 上可以通过 systemd drop-in 为 Docker 服务注入代理环境变量。下面的地址假设 Clash 在宿主机监听 mixed port 7890,并且 Docker daemon 可以访问这个地址。
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, docker-registry.example.com"
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo systemctl show --property=Environment docker
HTTPS_PROXY 的值仍然可以写成 http://,因为这里表示通过 HTTP CONNECT 隧道转发 HTTPS 请求,并不代表 Docker Hub 使用明文 HTTP。NO_PROXY 则用来排除本地回环、内网仓库和不需要代理的地址。不要把过大的网段随意加入 NO_PROXY,否则 Docker 可能绕过 Clash 直接连接外部仓库。
修改服务配置后,一定要重启 daemon,仅仅重新打开终端不会让已经运行的后台进程读取新环境变量。可以先查看 Docker 服务是否成功启动,再执行带详细输出的测试:
docker info
docker -D pull alpine:latest
如果配置生效,daemon 的日志或调试输出通常会表现为连接能够进入认证和 manifest 阶段。若仍然超时,检查 Clash 面板的连接记录:完全没有 auth.docker.io 或 registry-1.docker.io 请求,说明代理变量没有被 daemon 使用;出现请求但节点连接失败,才进入 Clash 节点和规则层排查。
Docker Desktop 与 WSL2 要单独确认代理入口
Docker Desktop 的 daemon 通常不等同于宿主机上的 systemd Docker 服务。Windows 或 macOS 上,即使 Clash 在宿主机的回环地址监听端口,Desktop 内部的 daemon 也可能无法直接访问该回环地址。应在 Docker Desktop 的设置中查找 Resources、Proxies 或 Network 相关页面,使用 Desktop 支持的代理配置方式,而不是照搬 Linux 的 /etc/systemd/system/docker.service.d。
如果 Clash 运行在 Windows 主机而 Docker daemon 位于 WSL2 或 Desktop 虚拟机,需要让 Clash 开启“允许局域网连接”,监听地址不能只绑定在 127.0.0.1。然后使用宿主机在该虚拟网络中的可达地址和代理端口,例如 http://host.docker.internal:7890 或由环境实际分配的宿主机网关地址。不同 Docker Desktop、WSL2 网络模式的名称和可达性可能不同,不要机械套用某个固定 IP。
# 在能够访问 Docker daemon 的环境中测试代理入口
curl -v -x http://host.docker.internal:7890 \
https://auth.docker.io/token?service=registry.docker.io
这条命令的重点不是返回内容长什么样,而是观察是否能完成 TLS 连接并收到 HTTP 响应。如果地址不可达,先修正 Clash 的监听范围、防火墙和虚拟机到宿主机的路由;如果能响应但 docker pull 仍失败,再检查 Docker Desktop 是否真的采用了同一代理配置。
Clash 配置:端口、DNS 与 Docker Hub 规则一起处理
Clash 或 mihomo 侧至少要准备一个可供 daemon 使用的 HTTP 代理入口。最简单的是 mixed port,它同时接受 HTTP 代理和 SOCKS5 请求;如果还要接管不认识代理协议的 TCP 流量,才需要进一步考虑 redir、TProxy 或 TUN。下面是一段用于说明结构的配置片段,代理组名称需要替换成当前 Profile 中真实存在的名称:
mixed-port: 7890
allow-lan: true
mode: rule
log-level: info
dns:
enable: true
listen: 0.0.0.0:1053
enhanced-mode: fake-ip
nameserver:
- https://1.1.1.1/dns-query
- https://8.8.8.8/dns-query
fallback:
- tls://1.1.1.1
fallback-filter:
geoip: true
rules:
- DOMAIN-SUFFIX,docker.io,Proxy
- DOMAIN-SUFFIX,docker.com,Proxy
- DOMAIN,auth.docker.io,Proxy
- DOMAIN-KEYWORD,docker,Proxy
- MATCH,DIRECT
这里的 Proxy 只是示例策略组名,如果配置中实际叫“自动选择”或“全球代理”,必须保持完全一致。规则从上到下匹配,而且命中后停止继续匹配。如果前面已有一条宽泛的 GEOIP,CN,DIRECT 或某个国内域名直连规则,可能在 Docker Hub 的域名解析成特定 IP 后把请求错误导向 DIRECT。排查阶段可以临时把 Docker 相关域名规则放到更靠前的位置,并在连接面板确认最终命中的策略。
DNS 也不能忽略。Docker daemon 可能先通过宿主机 DNS 解析仓库域名,解析成功后才把 IP 交给 HTTP 代理;而容器内部的 DNS 又可能经过 Docker 内置转发器。若解析结果被污染,Clash 规则即使正确也可能无法按域名工作。启用 Clash DNS 后,要确认实际使用路径,不要只看 YAML 中写了 dns.enable: true。连接日志、nslookup、Docker daemon 日志三者应互相印证。
需要透明接管容器流量时:先设计出口,再启用 TUN
当需求从“拉取镜像”扩大到“容器内的 apt、npm、curl 以及运行服务都要自动经过 Clash”时,显式代理变量就不够了,这时才考虑透明代理。常见方案有三种:让每个容器显式使用代理;让 Docker bridge 流量经过宿主机上的 redir 或 TProxy;把 Clash 放进专门的网关容器并让其他容器使用它的网络出口。三者的维护成本依次升高,不建议为了修复一个 Docker Hub 超时就直接改宿主机全部转发规则。
使用 mihomo TUN 时,典型结构包括开启虚拟网卡、自动路由、DNS 劫持和正确的出站接口检测:
tun:
enable: true
stack: mixed
auto-route: true
auto-detect-interface: true
dns-hijack:
- any:53
- tcp://any:53
这段配置只说明 Clash 的 TUN 能力,不保证自动覆盖 Docker 的每一种网络模式。Docker bridge 的数据包可能经过宿主机的转发链,Docker Desktop 则可能经过额外的虚拟机边界;如果 TUN 自动路由没有处理这些路径,容器流量仍会绕过 Clash。透明接管还必须避免回环:Clash 自己发往代理节点的连接不能再次被 TUN 或 iptables 重定向,否则会形成代理套代理,表现为节点连接超时、CPU 占用升高或所有请求循环失败。
在 Linux 上继续做 TProxy 或 redir 时,还要处理转发权限、策略路由、Docker 自己的 NAT 链以及 Clash 进程 UID 排除。不同发行版的 nftables、iptables 兼容层和 Docker 版本差异很大,建议先用一台测试主机验证,保留现有规则,并把“代理进程流量、局域网网段、Docker 网关、DNS 请求”分别列为独立的排除项。不要直接复制一套不理解的全局重定向脚本,一旦规则顺序错误,宿主机 SSH、Docker DNS 和代理节点连接可能同时中断。
按请求阶段排查超时与无法访问
配置完成后,建议从最底层到最上层逐步验证,每一步只改变一个变量。首先确认 Clash 的代理端口正在监听,并且监听地址对 Docker daemon 可达:
ss -lntp | grep 7890
curl -v -x http://127.0.0.1:7890 https://registry-1.docker.io/v2/
curl -v -x http://127.0.0.1:7890 \
"https://auth.docker.io/token?service=registry.docker.io"
访问 registry 的 /v2/ 返回 401 Unauthorized 并不一定是失败,这通常说明网络和 TLS 已经到达仓库,仓库只是要求客户端继续提供认证信息。真正需要警惕的是连接超时、无法解析主机、代理拒绝连接或 TLS 握手在中途断开。
- 代理端口无连接记录:检查 Docker daemon 的代理环境变量、Docker Desktop 的代理设置和 daemon 是否已重启。
- 只有 auth.docker.io 失败:检查规则是否只覆盖了
registry-1.docker.io,并确认认证域名没有被直连规则提前命中。 - manifest 成功,layer 下载超时:查看连接面板中的实际 CDN 域名,不要只盯着 Docker Hub 主域名;同时检查节点带宽、MTU 和长连接稳定性。
- 域名解析失败:分别测试宿主机、Docker daemon 所在环境和临时容器的 DNS,确认是否为不同的解析路径。
- 容器内 curl 正常但 docker pull 失败:说明容器网络并非关键故障点,优先回到 daemon 代理或 Docker Desktop 虚拟机配置。
- docker pull 正常但容器访问失败:不要继续修改 daemon 代理,应检查容器的 HTTP_PROXY、TUN、bridge 转发或应用自身是否支持代理。
还可以用一个最小镜像验证问题是否只出现在特定仓库或大文件下载阶段:
docker pull hello-world:latest
docker pull alpine:latest
docker system info
如果小镜像成功而大型镜像失败,重点观察分层下载的 CDN 连接和代理节点稳定性;如果所有镜像都在认证前超时,重点检查 auth.docker.io 和 daemon 的代理入口。不要连续快速重复执行几十次拉取,这会制造大量失败连接,也可能触发仓库侧限流,让原本清晰的故障变得更难判断。
让配置长期稳定:减少透明代理的副作用
实际使用中,推荐把 Docker 流量分成三种明确场景管理。第一种是 Docker daemon 的镜像拉取,使用独立的 HTTP(S) 代理配置;第二种是构建阶段访问包管理器,通过 BuildKit 或构建参数为构建过程显式传入代理;第三种是运行中的容器应用,根据应用是否支持代理,选择环境变量、sidecar 网关或透明接管。把三类流量混在一套全局转发脚本里,虽然短期看起来省事,但后续遇到 DNS、内网服务和代理回环问题时很难定位。
- 保留清晰的端口用途:例如把
7890作为 mixed port,把 redir 或 TProxy 端口单独使用,不要让多个程序共同抢占同一监听端口。 - 记录当前网络拓扑:写清 Clash 运行在宿主机、WSL2、Docker 容器还是 Desktop 虚拟机,同时记录 daemon 到代理入口的实际地址。
- 规则优先匹配 Docker 域名:至少覆盖 registry、认证和实际下载域名,通过连接日志持续补充,不要只凭首页域名猜测。
- 为内网地址设置 NO_PROXY:私有 registry、局域网服务和 Docker 内部网段不应无条件绕到外部代理,否则会增加延迟或造成内网不可达。
- 分阶段验证改动:先确认显式代理可用,再启用 DNS 接管,最后才尝试 TUN 或 TProxy,每一步都保留回滚方法。
归纳起来,Docker Hub 拉取超时通常不是单一的“Clash 节点不好”,而是 Docker daemon、虚拟化网络、DNS、认证域名和代理规则之间没有接上。先用显式 daemon 代理解决镜像拉取,再根据容器业务是否需要全流量接管来选择 TUN 或透明代理,能够把故障范围控制在最小,也能避免为了一个仓库访问问题破坏整台主机的网络路由。