先确认镜像拉取经过哪条链路
Docker 拉取镜像超时,不能只看 Clash 当前节点的延迟。执行 docker pull nginx:latest 时,真正发起请求的通常是宿主机上的 Docker daemon,而不是正在运行的业务容器。Docker CLI 只是通过本地 Unix Socket 或远程 API 向 daemon 发送指令,随后由 daemon 访问镜像仓库、认证服务和分层文件地址。
这意味着,在容器内设置 HTTP_PROXY、HTTPS_PROXY,并不一定能解决 docker pull 超时;反过来,为 Docker daemon 配置代理,也不会自动让容器内的应用经过代理。两者是不同的数据流,排查时必须分别验证。
| 操作 | 实际发起连接的组件 | 常见配置位置 |
|---|---|---|
docker pull |
宿主机 Docker daemon | systemd 服务环境、daemon 配置或宿主机透明代理 |
构建镜像时执行 apt、npm |
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-port 或 tproxy-port。应用不需要理解代理协议,但规则必须正确处理 Docker 网桥、代理进程自身流量和本地目标,否则容易出现代理环回、DNS 失败或容器完全断网。
- 只解决镜像拉取:优先为 Docker daemon 配置显式 HTTP/HTTPS 代理。
- 容器内多个程序都要代理:可使用 Docker 网关加显式代理,或配置针对 bridge 网段的透明转发。
- 需要代理 UDP 或不支持代理的程序:考虑 TProxy 或 mihomo TUN,但必须额外规划路由和 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 update、npm install 或 go 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.x、10.x 或 192.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 请求也可能让容器解析到错误地址。实际规则通常需要包含以下排除项:
- 本机回环地址与 Docker 网桥网关。
- RFC1918 私有网段中不需要代理的内部服务。
- mihomo 的入站端口、控制端口和代理进程自身流量。
- Docker DNS 地址
127.0.0.11,避免破坏容器服务发现。 - 已经明确使用直连策略的镜像仓库或企业内网域名。
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.io、auth.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 网络。
- 节点选择:用持续下载而不是单次延迟判断质量。镜像 layer 下载容易暴露丢包、限速和连接复用问题。
- 规则顺序:把镜像仓库、认证域名和 CDN 相关规则放在宽泛的直连或代理规则之前。
- 连接超时:不要盲目设置极大的超时。先区分 DNS 慢、TCP 建连慢和传输中断,否则只会让失败等待更久。
- MTU:TUN、WireGuard、QUIC 或多层隧道可能减少有效 MTU。出现小请求正常、大 layer 卡住时,应检查分片与重传。
- 并发下载:多个 layer 同时下载会放大节点限速。代理节点或出口较弱时,可降低并发并观察整体吞吐是否反而提高。
- 缓存与镜像加速:团队环境可部署可信的 Registry mirror,减少每台主机重复通过代理下载相同 layer。
最后做一次分层验证:先用宿主机 curl 测试 Registry,再用显式代理执行 docker pull,然后关闭显式代理测试透明转发,最后检查容器运行时的外网访问。每次只改变一个变量,并记录代理日志中的目标域名、策略组和出站节点。这样才能判断问题究竟来自 DNS、Docker 网关、代理端口、规则匹配,还是镜像仓库自身的认证与限速。