在开发机上运行 Docker 时,宿主机浏览器已经通过 Clash 访问 GitHub,并不代表容器里的 git、npm、pip 或 Docker Engine 也会自动使用代理。容器拥有独立的网络命名空间,Docker bridge、宿主机路由、DNS 解析和 Clash 入站端口之间只要有一环没有接好,就可能出现「宿主机能打开、容器超时」「GitHub 正常、Docker Hub 失败」或「规则写了却始终 DIRECT」等现象。本文从容器网络与代理数据流出发,搭建一套适用于开发环境和无头服务器的 Clash Docker 透明代理方案,并把 GitHub、镜像仓库、npm、pip 与 Docker Hub 拆成可维护的分流规则。

这里的「透明代理」并不只指 TUN。对 Docker 来说,它可以是三种不同层次:让容器显式使用 Clash 的 HTTP 代理端口,让宿主机通过 REDIRECT 或 TPROXY 接管容器流量,以及让容器使用宿主机上的 Mihomo 透明入口。三者的复杂度、兼容性和排障方式不同。建议先用显式代理验证链路,再逐步过渡到透明接管;不要一开始就同时启用 Docker 自定义网络、旁路 DNS、TPROXY 和多份防火墙规则。

先看清数据流:容器请求到底经过哪些环节

典型路径是:容器内的进程发起 HTTPS 请求,Docker 把数据送到 bridge 网桥,宿主机内核根据路由表转发,随后由 Clash 的监听端口或透明代理规则接收,Clash 再依据域名、IP 和规则集选择策略组,最后通过节点访问远端服务。任何一步配置错误,最终都可能只表现为 ETIMEDOUT,所以不能只盯着应用日志。

最容易被忽略的是 127.0.0.1 的含义。容器里的 127.0.0.1 指向容器自身,并不是宿主机;因此在容器中设置 HTTPS_PROXY=http://127.0.0.1:7890,只有当 Clash 也运行在同一个容器里才有意义。若 Clash 运行在宿主机,应使用宿主机在 Docker bridge 上可达的地址,或在较新的 Docker 环境中使用 host.docker.internal。Linux 上这个主机名不一定默认存在,通常需要显式添加:

docker run --add-host=host.docker.internal:host-gateway \
  -e HTTP_PROXY=http://host.docker.internal:7890 \
  -e HTTPS_PROXY=http://host.docker.internal:7890 \
  your-image:latest

同时,Clash 的入站监听地址不能只绑定在 127.0.0.1。宿主机上的 Docker 容器无法访问一个仅对宿主回环接口开放的端口。应在配置中确认 mixed-port 或对应端口监听于 0.0.0.0,并使用宿主机防火墙限制来源范围,避免把代理端口暴露到公网。

安全提醒:宿主机代理端口只应允许 Docker 网段或可信局域网访问。不要为了让容器「先通起来」而把带有代理能力的端口直接映射到云服务器公网地址;无认证的开放代理很容易被扫描、滥用并产生额外流量。

模块化 YAML:把代理入口、策略组与规则提供者分开

一份适合 Docker 场景的 Clash 配置,至少应拆成四个逻辑部分:代理入站、策略组、规则提供者和规则顺序。入站解决「流量如何进来」,策略组解决「进入后交给谁」,rule-providers 解决「规则如何更新」,规则顺序则决定 GitHub 与镜像域名会不会被过宽的 GEOIP 或 MATCH 条目抢先匹配。

下面是结构示意,代理节点部分请替换成你自己的订阅内容。这里使用 mixed-port,因为它同时兼容 HTTP 和 SOCKS5 客户端;对于只会读取 HTTP_PROXY 的 npm、pip 和多数 CI 工具,混合端口比单独暴露 SOCKS 端口更省事。

mixed-port: 7890
allow-lan: true
bind-address: '*'
mode: rule
log-level: info
external-controller: 0.0.0.0:9090

proxies:
  - name: "dev-proxy"
    type: ss
    server: example.com
    port: 443
    cipher: aes-128-gcm
    password: "change-me"

proxy-groups:
  - name: DEV-PROXY
    type: select
    proxies:
      - dev-proxy
      - DIRECT

rule-providers:
  github:
    type: http
    behavior: domain
    url: https://example.invalid/rules/github.yaml
    path: ./ruleset/github.yaml
    interval: 86400
    format: yaml
  registries:
    type: http
    behavior: domain
    url: https://example.invalid/rules/registries.yaml
    path: ./ruleset/registries.yaml
    interval: 86400
    format: yaml

rules:
  - RULE-SET,github,DEV-PROXY
  - RULE-SET,registries,DEV-PROXY
  - DOMAIN-SUFFIX,github.com,DEV-PROXY
  - DOMAIN-SUFFIX,githubusercontent.com,DEV-PROXY
  - DOMAIN-SUFFIX,docker.io,DEV-PROXY
  - DOMAIN-SUFFIX,npmjs.org,DEV-PROXY
  - DOMAIN-SUFFIX,pypi.org,DEV-PROXY
  - GEOIP,CN,DIRECT
  - MATCH,DIRECT

生产环境不要把所有外部站点都粗暴地写进一个名为「国外」的巨大规则集。GitHub 的网页、Raw 文件、Release 下载和容器镜像可能使用不同的主机名;镜像仓库也可能使用重定向或对象存储域名。把规则按用途拆开,出现问题时可以在 Clash 日志中快速判断是 GitHub 规则、Registry 规则还是 DNS 解析出了偏差。

用途常见域名建议策略注意事项
代码托管github.comgithubusercontent.comDEV-PROXYRaw、Release 下载可能跳转到 CDN
JavaScript 包npmjs.orgregistry.npmjs.orgDEV-PROXY 或镜像优先确认项目是否指定了内部 registry
Python 包pypi.orgfiles.pythonhosted.orgDEV-PROXY 或镜像索引页与文件下载域名可能不同
容器镜像docker.ioregistry-1.docker.ioDEV-PROXY鉴权端点可能位于另一个域名

rule-providers 与 DNS:避免规则看似正确却无法命中

rule-providers 的价值不只是减少 YAML 长度,更重要的是让域名集合可以独立更新。定义提供者时要核对三项:远端文件格式与 behavior 是否一致,path 所在目录是否可写,以及容器重启后规则文件是否仍然存在。若把规则文件写在临时层,镜像更新或容器重建后可能丢失缓存,启动阶段又因为无法下载规则而回退到旧配置。

behavior: domain 适合包含域名或域名后缀的集合;如果远端文件是完整的 Clash 规则行,就应使用与其内容匹配的行为类型。不要只复制一段网上 YAML 而不检查格式。启动后可在 Clash 控制面板查看 rule-provider 是否加载成功,也可以进入容器检查对应路径和更新时间。

DNS 方面,Docker 默认可能使用宿主机转发器或 Docker 内置 DNS。若容器首先在本地解析出一个不稳定的地址,Clash 后续就可能只能看到 IP,无法按域名规则正确分流。使用 Mihomo 时,应根据你的网络环境选择 fake-ip 或 redir-host,并确保容器的 DNS 请求没有绕过 Clash。对于必须直连的内网域名,可以加入 fake-ip-filter;对于 GitHub、npm、PyPI 和 Docker Hub 这类需要代理的域名,不要随意加入排除列表。

在无头服务器上还要注意 Docker Compose 的 dns: 配置。手动指定公共 DNS 并不等于解析一定更快,反而可能让容器绕过宿主机的分流 DNS。更稳妥的做法是先确认宿主机、Docker 容器和 Clash 使用的 DNS 路径,再决定是否固定 DNS 地址。排障时同时记录「查询结果」「连接面板显示的主机名」和「最终策略」,不要只用 ping 判断 HTTPS 服务是否可用。

动手配置:用 Compose 让开发容器经过 Clash

下面用显式代理方式完成第一轮验证。它不是最彻底的透明接管,却能把代理端口、容器出口和应用行为逐项拆开,特别适合先验证 GitHub、npm、pip 和镜像仓库是否真的命中规则。假设 Clash 在宿主机的 7890 端口监听,Docker Compose 文件可以这样写:

services:
  builder:
    image: node:22
    working_dir: /workspace
    volumes:
      - ./:/workspace
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      HTTP_PROXY: http://host.docker.internal:7890
      HTTPS_PROXY: http://host.docker.internal:7890
      NO_PROXY: localhost,127.0.0.1,.local,registry.intra.example
    command: ["sleep", "infinity"]

启动后不要直接运行完整构建,先执行最小测试。首先检查环境变量是否进入容器,再访问一个 HTTPS 地址;随后使用 Git 查看远端,最后分别测试 npm 或 pip。每一步都应在 Clash 连接面板中看到对应域名,并确认策略不是意外的 DIRECT

docker compose up -d
docker compose exec builder sh
env | grep -i proxy
curl -I https://github.com
git ls-remote https://github.com/example/project.git
npm view lodash version
python -m pip index versions requests

Docker 镜像拉取是另一个层次。即使容器内的 curl 已经成功,docker pull 仍可能失败,因为真正发起请求的是宿主机上的 Docker daemon,而不是普通业务容器。应在 Docker daemon 的配置中设置 HTTP/HTTPS 代理,或按发行版文档使用 systemd drop-in,然后重载服务并重新拉取。修改后先用一个小镜像测试,不要在生产构建高峰期直接重启 Docker 服务。

如果你需要真正的透明接管,可让 Mihomo 运行在宿主机或独立网络容器中,并把 Docker bridge 网段纳入 TPROXY 或 REDIRECT 规则。此方案要求正确处理转发开关、策略路由、回环流量、容器 DNS 以及代理自身的排除项。Docker 的 network_mode: host 可以减少一部分网络隔离,但会牺牲端口隔离和可移植性,不应把它当成所有服务的默认答案。

推荐顺序:先用 HTTP_PROXY 验证应用,再让 Docker daemon 走代理,最后才考虑 TPROXY。这样可以明确区分「应用不读取代理变量」「daemon 没有代理」和「内核透明规则错误」三类问题。

按日志排查:GitHub、npm、pip 和 Docker Hub 为什么会超时

第一步看域名是否出现。若 Clash 连接面板完全没有请求,说明应用没有读取代理变量、容器无法连到宿主机端口,或请求走了另一条网络路径。此时在容器里检查宿主机地址、端口连通性和环境变量,不要先修改规则。

第二步看最终策略。若 github.comregistry.npmjs.orgpypi.org 显示 DIRECT,通常是规则顺序、rule-provider 未加载、域名被错误解析成 IP,或某个更早的规则抢先匹配。将具体域名临时写在自定义规则最前面,确认链路恢复后,再把它整理进模块化 provider。

第三步区分主站与下载站。GitHub 页面能打开,不代表 Release 资产或 Raw 文件一定成功;npm 与 pip 的索引页能访问,也不代表 tarball、wheel 文件下载域名没有被直连。Docker Hub 则经常涉及 registry、鉴权和镜像层下载多个请求。应在连接日志中连续观察一次完整操作,记录实际出现的主机,而不是凭经验只添加一个域名。

第四步检查 NO_PROXY。如果把过大的后缀写入 NO_PROXY,例如误把整个公共域名范围加入其中,应用会绕过 Clash;如果完全没有内网排除项,企业 Git、内部 npm registry 或本地服务又可能被送进外部节点。建议只填写明确的回环地址、Docker 内部网段和确实需要直连的内部域名。

最后核对证书与时间。容器时间偏差、企业 TLS 检查、宿主机防火墙和节点出口限制,都可能制造类似超时的表现。用 curl -v 观察连接建立、TLS 握手和响应阶段;若 TCP 很快建立但返回 401、403 或 429,就应转向鉴权、权限或服务端限流,而不是继续修改 Clash 规则。

无头服务器上的稳定性与安全清单

开发环境追求快速验证,无头服务器则更看重可重复性。首先固定 Clash 配置与 rule-provider 的版本来源,给远端规则设置合理更新间隔,并保留一份最近可用的本地缓存。规则下载失败时不应让服务完全失去出网能力,必要时可以在部署流程中先校验 YAML,再滚动重启代理容器。

其次限制管理面板。external-controller 不应无保护地监听公网;为控制 API 配置密钥,并通过防火墙只允许管理网段访问。Docker Compose 中不要把订阅链接、代理密码和控制器密钥直接提交到公开代码仓库,可以使用环境变量、秘密管理或单独的未跟踪文件。

再次做好分层监控:宿主机监控 Clash 进程和节点连接,Docker 监控容器的 DNS 与出口,应用日志则记录具体失败域名和重试次数。对于 CI,建议设置有限的重试与超时,避免一个不可达节点让整个流水线无限等待。对于镜像构建,可优先配置可信的内部镜像缓存,同时保留 Docker Hub 的代理规则作为回退路径。

同类的单一 HTTP 代理脚本往往只能覆盖明确读取环境变量的程序,遇到 Docker daemon、静态编译二进制或多域名下载流程就需要反复补丁;一些仅面向桌面的代理工具也不适合无头服务器长期运行。相比之下,Clash 配合模块化 rule-providers、可观察的连接日志和可选择的 TUN/透明接管,能够把 GitHub、npm、pip 与 Docker Hub 分别纳入可审计的分流策略;如果你正在寻找一套可在桌面开发机与服务器之间复用的代理基础,先从本文的显式代理方案验证,再前往下载页选择适合平台的 Clash 客户端会更稳妥。

立即免费下载 Clash,开启流畅上网新体验 →