Docker 镜像拉取超时,通常不只是「给 Docker 配一个代理地址」这么简单。Docker 守护进程、容器内部进程和宿主机上的 Clash 可能处在不同的网络命名空间中,浏览器能够正常访问 Docker Hub,并不代表 docker pull 或容器内的 curl 也会自动经过代理。本文从数据流和网络架构出发,系统讲解如何使用 Clash 为 Docker 主机与容器提供透明代理,重点覆盖 TUN、iptables、DNS、daemon.json、YAML 分流 以及常见故障排查方法。
先理解 Docker 与 Clash 的数据流
在默认的 Docker bridge 网络中,容器通常通过宿主机上的虚拟网桥 docker0 访问外部网络。容器发出的数据包会经过 veth pair、Docker 网桥、宿主机路由和 NAT,最后从物理网卡发送出去。Clash 如果只监听 127.0.0.1:7890,只能接收宿主机本机应用主动连接的代理请求,并不会自动接管这些经过转发的数据包。
一次典型的 docker pull 可能包含以下几个阶段:
- Docker daemon 解析镜像仓库域名,例如
registry-1.docker.io。 - Docker daemon 访问认证服务,获取 Registry token。
- Docker daemon 连接镜像仓库,读取 manifest 和 layer 列表。
- Docker daemon 并发下载多个镜像 layer,并写入本地存储。
这里有一个容易忽略的重点:执行 docker pull 的客户端只是向 Docker daemon 发送请求,真正访问外网的是 daemon。即使你在当前 Shell 中设置了 http_proxy,也不一定会影响后台运行的 Docker 服务。因此,透明代理和 daemon 代理是两条不同的配置路径。
选择 daemon 代理还是透明代理
Docker daemon 代理的优点是简单、稳定、容易回滚。它只影响 Docker 守护进程访问外部 Registry 的流量,不会改变所有容器的默认路由,也不会接管宿主机上的其他服务。对于 CI 服务器、构建机和只需要拉取镜像的主机,这是最推荐的第一步。
透明代理适用于更复杂的场景,例如容器需要访问 GitHub、外部 API、软件仓库或其他需要按域名分流的服务。它不要求应用显式支持 HTTP 或 SOCKS 代理,但需要正确处理转发、回环、DNS 和代理服务器自身的排除规则,配置错误时容易出现连接环路。
| 方案 | 作用范围 | 优点 | 主要注意事项 |
|---|---|---|---|
| daemon.json | Docker daemon | 配置简单,适合镜像拉取 | 不会自动代理容器内的其他请求 |
| 环境变量 | 单个命令或容器 | 临时测试方便 | 容易遗漏后台服务,敏感信息可能进入日志 |
| Clash TUN | 宿主机及可路由流量 | 支持 TCP/UDP,应用无需代理设置 | 需要内核、路由和 DNS 配合 |
| iptables 透明转发 | 指定接口或网段 | 可精确接管 Docker 网桥流量 | 必须排除 Clash 自身、局域网和保留地址 |
实际部署时,可以先使用 daemon 代理确认 Docker Hub 可用,再逐步加入 TUN 或透明转发。这样能够把「Registry 本身不可用」「代理节点不可用」和「iptables 规则错误」分开验证,避免一次修改多个组件后无法判断问题来源。
配置 Docker daemon 代理
在 Linux 主机上,可以通过 systemd drop-in 为 Docker 服务设置代理。使用这种方式时,代理地址必须是 Docker daemon 能够访问的地址。如果 Clash 运行在宿主机上,通常不能填写容器内部的 127.0.0.1;对于 Docker daemon 来说,宿主机回环地址仍然是宿主机本身,但如果 Clash 运行在另一个容器中,就必须使用可达的容器地址或服务名。
创建配置目录并编辑服务环境文件:
sudo mkdir -p /etc/systemd/system/docker.service.d sudo nano /etc/systemd/system/docker.service.d/proxy.conf
下面的示例假设 Clash 在宿主机监听 7890 HTTP 代理端口。请根据实际客户端修改端口;如果只开放了 SOCKS5 端口,Docker daemon 的兼容性和写法可能不同,优先使用 HTTP 代理端口。
[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,172.16.0.0/12,192.168.0.0/16,10.0.0.0/8"
写入后重新加载 systemd 并重启 Docker:
sudo systemctl daemon-reload sudo systemctl restart docker sudo systemctl show --property=Environment docker docker pull hello-world
如果 Docker 使用的是 rootless 模式,服务名称和配置路径可能是用户级 systemd 服务,需要使用 systemctl --user 重新配置。修改 /etc/docker/daemon.json 也可以设置代理,但不同 Docker 版本对字段支持存在差异,systemd 环境变量方式通常更容易观察和回滚。无论采用哪种方式,修改后都应检查 journalctl -u docker,确认 daemon 没有因为 JSON 格式或环境变量错误而启动失败。
使用 Clash TUN 接管宿主机与容器流量
在 Mihomo 或支持 TUN 的 Clash 客户端中,TUN 会创建一个虚拟网络接口。系统把需要代理的连接路由到该接口,Clash 再根据规则决定直连或转发到代理节点。相比单纯监听 HTTP/SOCKS 端口,TUN 可以处理不支持代理设置的程序,也更适合接管 Docker 产生的原始 TCP 流量。
一个基础的 Mihomo TUN 配置如下:
tun: enable: true stack: system device: mihomo auto-route: true auto-detect-interface: true dns-hijack: - any:53 dns: enable: true enhanced-mode: fake-ip fake-ip-range: 198.18.0.1/16 nameserver: - 223.5.5.5 fallback: - tls://1.1.1.1:853 - tls://8.8.8.8:853
TUN 配置中的 auto-route 负责添加路由,auto-detect-interface 用于识别真实出口网卡,dns-hijack 则把经过系统的 DNS 请求交给 Clash。不同 Mihomo 版本对字段和默认行为可能存在差异,导入配置后应在日志中确认 TUN 设备是否创建成功,而不是只看客户端界面上的开关状态。
Docker 的自定义 bridge 网段可能被 TUN 当作普通流量处理。如果发现容器无法访问宿主机服务,或者容器访问代理时出现循环,可将 Docker 网段加入直连或排除列表。常见网段包括 172.16.0.0/12、192.168.0.0/16 和局域网实际使用的网段,但不要机械照搬,应该以 docker network inspect bridge 的结果为准。
避免代理环路与局域网误代理
透明代理最常见的严重错误是把 Clash 连接代理节点本身的流量再次转发回 TUN。结果通常表现为节点全部超时、日志中连接数量不断增加,或者 Docker Hub 访问时出现反复重试。应确保代理服务器 IP、宿主机网关、Docker 网桥和本地 DNS 服务不会被错误地送入代理链。
- 将代理节点的固定 IP 加入直连或绕过 TUN 的列表。
- 将 Docker bridge、宿主机局域网和管理网段加入排除范围。
- 不要让
fake-ip-range与 Docker 网络、物理局域网发生重叠。 - 测试阶段先关闭复杂的 Rule Provider,确认基础链路正常后再逐个加入。
使用 iptables 接管 Docker 网桥流量
如果只开启 TUN 仍然无法接管容器流量,可以检查 Linux 的转发能力和 iptables 规则。Docker 通常会自动创建 NAT 规则,但透明代理还需要把指定流量重定向到 Clash 的透明端口。Mihomo 常见的透明入口包括 REDIRECT 或 TPROXY,具体端口和模式必须与 Clash 配置一致。
首先确认内核允许转发,并查看 Docker 网桥名称:
sysctl net.ipv4.ip_forward ip link show docker0 docker network inspect bridge
临时测试时可以先使用较小范围的规则,仅处理来自 docker0 的 TCP 流量。下面是示意写法,不能直接替代你的生产规则,因为不同发行版的链、优先级、Clash 透明端口和排除地址都可能不同:
sudo iptables -t nat -N CLASH_DOCKER sudo iptables -t nat -A PREROUTING -i docker0 -p tcp -j CLASH_DOCKER sudo iptables -t nat -A CLASH_DOCKER -d 127.0.0.0/8 -j RETURN sudo iptables -t nat -A CLASH_DOCKER -d 10.0.0.0/8 -j RETURN sudo iptables -t nat -A CLASH_DOCKER -d 172.16.0.0/12 -j RETURN sudo iptables -t nat -A CLASH_DOCKER -d 192.168.0.0/16 -j RETURN sudo iptables -t nat -A CLASH_DOCKER -p tcp -j REDIRECT --to-ports 7893
7893 只是示例透明端口,必须替换为你的 Clash 配置中实际监听的端口。若使用 TPROXY,则还需要策略路由、fwmark 和相应的 socket 选项,不能把 REDIRECT 规则和 TPROXY 配置混用。生产环境还应考虑 nftables、iptables-legacy 与 iptables-nft 的兼容性,并使用发行版支持的持久化工具保存规则。
DNS 与 YAML 分流配置
镜像拉取失败时,DNS 往往比代理节点更早出问题。Docker daemon 可能使用宿主机的 resolv.conf,容器则可能使用 Docker 内置 DNS 转发器。如果域名解析得到错误地址,后续即使 Clash 节点正常,也可能连接到不可用的 Registry。使用 TUN 时建议让 Clash 统一处理 DNS,并将本地域名、局域网域名和 Docker 内部服务排除在 fake-ip 之外。
Docker 相关域名可以通过规则明确指定策略组,避免被最后一条兜底规则错误地直连:
rules: - DOMAIN-SUFFIX,docker.io,Docker - DOMAIN-SUFFIX,docker.com,Docker - DOMAIN,registry-1.docker.io,Docker - DOMAIN,auth.docker.io,Docker - DOMAIN,production.cloudflare.docker.com,Docker - DOMAIN-SUFFIX,github.com,Proxy - DOMAIN-SUFFIX,githubusercontent.com,Proxy - GEOIP,CN,DIRECT - MATCH,Proxy proxy-groups: - name: Docker type: select proxies: - Proxy - DIRECT
Docker Hub 的实际内容下载地址可能由认证服务动态返回,不应只把 docker.io 加入规则后就认为全部流量已经覆盖。应打开 Clash 连接日志,观察认证请求、Registry 请求和 layer 下载请求分别命中了哪些域名与规则。对于企业私有仓库,则应将私有域名加入直连规则,避免内部镜像被发送到外部代理。
动手排查镜像拉取超时
建议按照从近到远、从简单到复杂的顺序执行以下步骤。每完成一步就重新测试,不要同时修改 DNS、iptables 和节点配置。
- 确认宿主机可以解析并访问 Registry:使用
getent hosts registry-1.docker.io和curl -I https://registry-1.docker.io/v2/。 - 确认 Clash HTTP 代理本身可用:执行
curl -x http://127.0.0.1:7890 -I https://registry-1.docker.io/v2/,观察返回状态和 Clash 日志。 - 确认 Docker daemon 读取到了代理环境:使用
systemctl show --property=Environment docker,然后重启 Docker。 - 确认容器 DNS:使用
docker run --rm busybox nslookup example.com,检查是否能稳定返回结果。 - 确认容器基础网络:使用
docker run --rm curlimages/curl -I https://example.com,区分 DNS 失败、TCP 超时和 TLS 错误。 - 最后再检查 TUN、iptables 计数器和透明端口监听状态,确认数据包确实进入 Clash。
如果宿主机的代理测试成功,但 docker pull 仍然超时,优先检查 daemon 配置与服务重启状态。如果 daemon 已成功拉取镜像,但容器内访问外部 API 失败,问题通常不在 daemon,而在容器默认路由、DNS 或透明转发。如果只有部分 layer 失败,则应查看 Clash 日志中的真实下载域名,并检查规则是否覆盖了动态 CDN 域名。
常见问题 FAQ
配置了 daemon 代理,为什么容器访问外网仍然失败?
daemon 代理只负责 Docker 服务自身的请求,例如拉取镜像和访问 Registry,不会自动设置每个容器的代理环境。容器需要单独设置 HTTP_PROXY、HTTPS_PROXY 和 NO_PROXY,或者通过 TUN、iptables 等方式实现透明代理。
容器中的 127.0.0.1:7890 为什么连不上 Clash?
容器中的 127.0.0.1 指向容器自身,不是宿主机。可以使用宿主机网关地址、Docker 提供的 host-gateway,或让 Clash 监听一个容器可达的宿主机地址。同时应限制监听范围并设置访问控制,避免把代理端口暴露到公网。
为什么能解析域名,却在 HTTPS 阶段超时?
这说明 DNS 可能正常,但 TCP、TLS 或路由环节仍有问题。应在 Clash 连接日志中确认请求是否被代理,检查代理节点 IP 是否被错误重定向,并确认防火墙没有阻断 Docker 网桥到透明端口的流量。
镜像可以拉取但速度很慢,应该改 DNS 还是换节点?
先区分延迟和带宽问题:如果认证请求就很慢,通常是节点或线路问题;如果认证很快但 layer 下载慢,可能是 CDN 域名没有命中正确策略、代理出口带宽不足,或 Docker 并发下载受到限制。通过 Clash 日志确认真实下载域名后,再调整规则和策略组,比盲目更换 DNS 更有效。
稳定的 Docker 代理应当具备清晰的边界:Docker daemon 的镜像请求走明确的代理策略,容器内部流量通过 TUN 或透明转发接管,局域网与 Docker 内部网段保持直连,DNS 则由 Clash 统一处理并避免泄漏。建议先完成 daemon 代理这一最小可用方案,再按实际需求启用 TUN 和 iptables。准备好后,可前往下载适合你平台的 Clash 客户端,并结合配置文件逐项验证。