Clash Docker透明代理进阶配置:解决镜像拉取超时

Docker 拉取镜像超时并不只是节点速度问题,还可能涉及容器网络、DNS 解析和代理链路。本文从数据流原理出发,配置 Clash/Mihomo 透明代理、Docker 网关与分流规则,并覆盖日志分析和性能调优。

先确认镜像拉取经过哪条链路

Docker 拉取镜像超时,不能只看 Clash 当前节点的延迟。执行 docker pull nginx:latest 时,真正发起请求的通常是宿主机上的 Docker daemon,而不是正在运行的业务容器。Docker CLI 只是通过本地 Unix Socket 或远程 API 向 daemon 发送指令,随后由 daemon 访问镜像仓库、认证服务和分层文件地址。

这意味着,在容器内设置 HTTP_PROXYHTTPS_PROXY,并不一定能解决 docker pull 超时;反过来,为 Docker daemon 配置代理,也不会自动让容器内的应用经过代理。两者是不同的数据流,排查时必须分别验证。

操作 实际发起连接的组件 常见配置位置
docker pull 宿主机 Docker daemon systemd 服务环境、daemon 配置或宿主机透明代理
构建镜像时执行 aptnpm BuildKit、构建容器或构建进程 构建参数、代理环境变量、BuildKit 配置
容器运行时访问外部 API 容器网络命名空间中的进程 容器环境变量、网关转发、TUN 或透明代理
宿主机访问镜像仓库 宿主机内核网络栈 系统代理、iptables/nftables、路由与 DNS

建议先分别执行以下测试。第一条检查宿主机是否能解析并建立 HTTPS 连接,第二条检查容器自身的 DNS 与出站网络,第三条确认 Docker daemon 当前是否能够读取代理环境:

curl -I --connect-timeout 10 https://registry-1.docker.io/v2/

docker run --rm busybox nslookup registry-1.docker.io

systemctl show --property=Environment docker

如果宿主机上的 curl 已经超时,优先处理 Clash 出站、DNS 或宿主机路由。如果宿主机访问正常而容器中的 DNS 失败,问题更可能出在 Docker bridge 网络或 DNS 转发。如果两者都正常,但 docker pull 仍然失败,则应查看 daemon 日志与认证域名,而不是继续更换节点。

配置 Clash 或 mihomo 的局域网与透明代理入口

Docker bridge 中的容器通常不能访问宿主机的 127.0.0.1。对容器而言,127.0.0.1 指向容器自身,而不是运行 mihomo 的宿主机。因此,Clash 仅监听回环地址时,容器无法连接到 127.0.0.1:7890。需要让代理监听宿主机可达的地址,并通过防火墙限制访问范围。

下面是一组适合 Linux 宿主机的基础字段。mixed-port 用于显式 HTTP/SOCKS 代理,redir-port 用于 TCP 透明转发;如果配置采用 TProxy,还需要根据内核与防火墙方案使用 tproxy-port。端口号可以调整,但后续 Docker 与防火墙规则必须保持一致。

mixed-port: 7890
redir-port: 7892
tproxy-port: 7893
allow-lan: true
bind-address: "*"
mode: rule
log-level: info

external-controller: 127.0.0.1:9090
secret: change-this-secret

allow-lan: true 只表示允许非本机来源连接入站端口,并不等于应该把控制接口暴露给局域网。控制接口仍建议绑定在 127.0.0.1:9090,不要把 external-controller 改成 0.0.0.0:9090。如果宿主机有多个网卡,也可以将代理监听地址限制为 Docker 网桥或内网接口,再用防火墙拒绝来自公网的访问。

显式代理与透明代理如何选择

显式代理最容易验证。Docker daemon、构建工具或容器应用明确设置代理地址,例如 http://172.17.0.1:7890,所有请求通过 HTTP CONNECT 或 SOCKS5 转发。它不会改变容器路由,也不需要修改 iptables,适合先验证节点和镜像仓库是否可用。

透明代理则由宿主机根据来源地址、目标端口或路由规则,将 TCP 流量重定向到 redir-porttproxy-port。应用不需要理解代理协议,但规则必须正确处理 Docker 网桥、代理进程自身流量和本地目标,否则容易出现代理环回、DNS 失败或容器完全断网。

为 Docker daemon 配置镜像拉取代理

如果目标只是让 docker pull 经由 Clash,显式配置 daemon 通常比直接修改透明代理规则更稳定。systemd 管理的 Docker 服务可以使用 drop-in 文件设置代理环境变量。先创建目录:

sudo mkdir -p /etc/systemd/system/docker.service.d

然后创建 /etc/systemd/system/docker.service.d/http-proxy.conf。这里的 172.17.0.1 只是常见 Docker bridge 网关示例,实际地址应以宿主机的 docker0 配置为准。若 mihomo 监听在宿主机内网地址,也可以填写该地址。

[Service]
Environment="HTTP_PROXY=http://172.17.0.1:7890"
Environment="HTTPS_PROXY=http://172.17.0.1:7890"
Environment="NO_PROXY=localhost,127.0.0.1,::1,172.17.0.0/16,10.0.0.0/8,192.168.0.0/16,registry.local"

修改后重新加载 systemd 并重启 Docker。重启 daemon 可能影响正在运行的容器,生产主机应先确认维护窗口和容器重启策略:

sudo systemctl daemon-reload
sudo systemctl restart docker

systemctl show --property=Environment docker
docker info

这里的代理地址使用的是 HTTP 形式,即使访问目标是 HTTPS,也由 HTTP 代理通过 CONNECT 建立隧道。若使用 SOCKS5,需先确认 Docker 版本与当前部署方式是否支持对应的代理格式;为了减少兼容性问题,通常优先使用 mihomo 的 mixed-port,并填写 http:// 地址。

NO_PROXY 很重要。内部镜像仓库、宿主机服务、私有网络和 Kubernetes API 不一定应该经过外部节点。忽略内网地址可能造成认证失败、延迟升高,甚至因为代理节点无法访问私有域名而导致部署中断。填写网段时要根据实际网络修改,不能机械复制示例中的地址。

处理 Docker build 与 BuildKit

daemon 能够拉取基础镜像,不代表 Dockerfile 中的 RUN apt-get updatenpm installgo mod download 一定能访问网络。构建步骤可能由 BuildKit 创建独立的构建容器,通常需要单独传递代理变量:

docker build \
  --build-arg HTTP_PROXY=http://172.17.0.1:7890 \
  --build-arg HTTPS_PROXY=http://172.17.0.1:7890 \
  --build-arg NO_PROXY=localhost,127.0.0.1,172.17.0.0/16 \
  -t demo-image:local .

Dockerfile 中可以声明对应参数,但不建议把包含用户名、密码或订阅令牌的代理 URL 固化到镜像层。构建完成后,使用 docker history 检查敏感信息是否意外进入历史记录。对于长期构建任务,更适合在 CI 的 secret、构建器配置或运行环境中注入代理,而不是直接写入版本库。

为 Docker bridge 配置透明转发与 DNS

透明转发的关键不是“把 7892 端口打开”,而是让 Docker 网桥发出的流量经过正确的重定向链。常见网桥名称是 docker0,网段可能是 172.17.0.0/16,但 Compose 自定义网络常见为其他 172.x10.x192.168.x 网段。先查看实际值:

ip -br addr show docker0
docker network inspect bridge
docker network ls

如果仅代理容器发出的 TCP 流量,可以将规则范围限制在 Docker 网段,并排除 mihomo 运行用户或代理进程产生的流量。不同发行版可能使用 iptables-nft、iptables-legacy 或原生 nftables,规则语法不能直接混用。下面只展示排查思路,不建议未经确认就将示例直接用于生产防火墙:

sudo iptables -t nat -S
sudo iptables -t nat -L -n -v
sudo nft list ruleset

透明代理还必须考虑目标地址属于内网、宿主机、Docker 网桥或代理节点本身的情况。把所有目标都重定向,可能导致访问 172.17.0.1:7890 时再次进入透明规则,形成环回;重定向 DNS 请求也可能让容器解析到错误地址。实际规则通常需要包含以下排除项:

DNS 是镜像拉取超时中很容易被忽略的一环。Docker 容器通常通过宿主机提供的 Docker DNS 解析器获得结果,宿主机的 /etc/resolv.conf、Docker 的 DNS 转发和 mihomo 的 DNS 模式可能同时参与。若日志中出现 lookup registry-1.docker.io: i/o timeout,这更像是解析链路问题;若已经解析出 IP 后出现 Client.Timeout exceeded while awaiting headers,才更应该检查 TCP 连接和代理出站。

可以从三个位置分别测试:

getent hosts registry-1.docker.io

docker run --rm busybox nslookup auth.docker.io

docker run --rm busybox wget -S -O - \
  --timeout=10 https://registry-1.docker.io/v2/

在使用 fake-ip 或 TUN 时,要确认镜像仓库域名没有被错误地映射到不可达地址,并确保规则能够识别 registry-1.docker.ioauth.docker.io 以及实际返回的 CDN 域名。镜像拉取不只访问一个固定域名,认证、清单和分层下载可能分别经过不同的主机名。

根据日志定位超时,并进行性能调优

配置完成后,不要只以“镜像拉下来了”作为判断标准。先打开 mihomo 的运行日志,再用一个体积较小的公共镜像测试。与此同时查看 Docker daemon 日志,确定失败发生在解析、认证、建立连接还是传输阶段:

sudo journalctl -u docker -f

docker -D pull hello-world

ss -lntp | grep -E '7890|7892|7893'
ip route
日志现象 可能位置 优先检查项
lookup ... i/o timeout DNS 或 DNS 转发 容器 resolv.conf、Docker DNS、mihomo DNS 监听
connection refused 代理端口未监听或地址错误 ss -lntp、监听地址、宿主机防火墙
context deadline exceeded 连接建立或传输超时 节点质量、代理规则、MTU、TCP 重传
401 Unauthorized 仓库认证阶段 认证域名是否可达、系统时间、代理是否篡改响应
unexpected EOF 连接中途被关闭 节点稳定性、HTTP/2、MTU、出口限速

性能调优应先减少不必要的链路复杂度。若 daemon 显式使用 172.17.0.1:7890 已经稳定,就不必同时启用 Docker bridge 透明转发和 TUN;多层代理会增加连接跟踪、DNS 转发与 MTU 问题。若必须使用 TUN,建议先让宿主机访问正常,再逐步接入一个测试容器,最后才扩展到全部 Docker 网络。

最后做一次分层验证:先用宿主机 curl 测试 Registry,再用显式代理执行 docker pull,然后关闭显式代理测试透明转发,最后检查容器运行时的外网访问。每次只改变一个变量,并记录代理日志中的目标域名、策略组和出站节点。这样才能判断问题究竟来自 DNS、Docker 网关、代理端口、规则匹配,还是镜像仓库自身的认证与限速。

FlClash 下载入口 查看各平台客户端