在 Docker 中运行 Clash 或 Mihomo,并不等于容器里的流量会自动经过代理。容器拥有独立的网络命名空间,应用看到的默认网关、DNS 服务器和路由表,通常都与宿主机不同。要实现真正可用的透明代理,必须同时处理容器网络、TUN 或 iptables 转发、DNS 解析、规则匹配以及权限隔离。本文从流量路径出发,搭建一套适合开发环境的 Clash Docker 透明代理方案,并重点说明 Git、npm、Docker Registry、语言包下载和 SSH 等工具如何分流,最后给出连接超时、DNS 泄漏与容器无法联网的排障方法。
先理解 Docker 透明代理的流量路径
普通的 Docker 网络大致是:容器通过虚拟网卡连接到 Docker bridge,数据包经宿主机上的网桥和 NAT 转换后访问外网。容器默认使用 Docker 内置 DNS(通常是 127.0.0.11),而代理程序如果只监听宿主机的 127.0.0.1,容器并不能直接访问它。
透明代理的目标,是让应用继续使用原来的连接方式,不需要为每个工具单独设置 HTTP_PROXY 或 HTTPS_PROXY。Clash 在网络层截获 TCP、UDP 或 DNS 请求,再依据域名、目标 IP 和进程无法直接获得的其他信息进行转发。常见实现方式有两种:
- TUN 模式:由 Mihomo 创建虚拟网卡,接管经过宿主机路由表的 IP 流量,配置相对集中,适合现代 Linux 和开发机。
- iptables 重定向:通过 NAT 规则把 TCP 流量转发到 Clash 的 redir-port 或 tproxy-port,兼容性较好,但需要手动处理排除地址、UID 和 DNS。
- 显式代理:应用设置代理环境变量后连接 Clash 的 mixed-port,最容易验证,但不是真正的透明代理,某些不支持代理的工具仍会绕过它。
如果只希望 Docker 容器访问代理,而不想改变宿主机全部流量,建议先采用共享宿主机网络或网关转发的方案验证链路,再逐步启用 TUN。若使用 network_mode: host,容器会直接使用宿主机网络命名空间,配置简单,但端口隔离能力下降;如果保留 bridge 网络,则需要让容器能够访问宿主机上的 Clash 监听地址。
准备 Docker Compose 与运行权限
下面示例使用 Mihomo 镜像。不同镜像的配置目录和启动参数可能略有区别,实际部署前请核对镜像文档;核心思路并不依赖某一个镜像名称。TUN 和 iptables 需要访问网络设备、修改路由表,因此容器通常需要 NET_ADMIN 能力以及访问 /dev/net/tun。
services:
mihomo:
image: metacubex/mihomo:latest
container_name: mihomo
restart: unless-stopped
network_mode: host
cap_add:
- NET_ADMIN
- NET_RAW
devices:
- /dev/net/tun:/dev/net/tun
volumes:
- ./config:/root/.config/mihomo
environment:
- TZ=Asia/Shanghai
宿主机需要先确认 TUN 设备存在:
ls -l /dev/net/tun docker compose up -d docker logs -f mihomo
如果系统没有 /dev/net/tun,可以尝试加载内核模块:
sudo modprobe tun lsmod | grep tun
生产环境不建议无条件使用 privileged: true。它会授予容器几乎全部主机权限,排障时虽然方便,却扩大了攻击面。优先使用 NET_ADMIN、NET_RAW 和 TUN 设备映射;只有在明确知道镜像需要额外能力时,才逐项增加权限。
配置 TUN、DNS 与自动路由
TUN 模式的关键是让 Mihomo 接管宿主机发出的 IP 流量,同时避免自己的出站连接再次被自己捕获。auto-route 负责注入路由,auto-detect-interface 用于自动识别真实网卡,strict-route 则让路由行为更严格。Docker 环境中如果发现宿主机无法联网,通常要先关闭严格路由或检查默认网卡名称。
mixed-port: 7890
redir-port: 7892
tproxy-port: 7893
allow-lan: true
bind-address: "*"
mode: rule
log-level: info
tun:
enable: true
stack: system
auto-route: true
auto-detect-interface: true
strict-route: false
dns-hijack:
- any:53
- tcp://any:53
dns:
enable: true
listen: 0.0.0.0:1053
ipv6: false
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
nameserver:
- https://223.5.5.5/dns-query
- https://doh.pub/dns-query
proxy-server-nameserver:
- tls://1.1.1.1:853
fake-ip-filter:
- "*.lan"
- "*.local"
- localhost
- "*.docker.internal"
dns-hijack 的作用是拦截应用直接发出的 53 端口请求,避免程序绕过 Clash 使用宿主机或运营商 DNS。fake-ip 能让域名规则在连接建立前生效,但局域网域名、服务发现和部分硬编码 IP 的程序可能不兼容,因此要通过 fake-ip-filter 排除。Docker 内部服务之间若依赖服务名解析,还应保留 *.local、内部域名或自定义后缀,不能把所有 DNS 请求一律交给外部解析器。
如果使用 bridge 网络而不是 network_mode: host,容器的 DNS 配置还需要单独处理。可以让应用容器把 DNS 指向宿主机在 Docker 网桥上的地址,或者在 Compose 中设置 dns;但必须确认该地址确实能访问 Clash 的 DNS 监听端口。仅仅把 listen 写成 0.0.0.0:1053,并不代表容器一定能访问,主机防火墙和 Docker 网络策略仍然可能拦截请求。
cat /etc/resolv.conf,再使用 nslookup github.com 或 dig github.com。同时观察 Mihomo 日志中是否出现 DNS 查询。如果容器能解析但日志没有请求,说明 DNS 仍然绕过了代理链路。为开发工具设计分流规则
开发环境常见的错误,是把所有流量都塞进同一个代理组。这样虽然容易配置,但会导致国内镜像访问变慢、公司内网无法连接,甚至让 SSH、数据库和 Kubernetes API 走到不适合的出口。更稳妥的方式是按用途拆分策略组,再按域名后缀和网络范围匹配。
proxy-groups:
- name: 开发代理
type: select
proxies:
- 自动选择
- DIRECT
- name: 自动选择
type: url-test
proxies:
- 节点-日本
- 节点-新加坡
url: https://www.gstatic.com/generate_204
interval: 300
tolerance: 80
- name: 国内直连
type: select
proxies:
- DIRECT
- 开发代理
rules:
- DOMAIN-SUFFIX,github.com,开发代理
- DOMAIN-SUFFIX,githubusercontent.com,开发代理
- DOMAIN-SUFFIX,githubassets.com,开发代理
- DOMAIN-SUFFIX,docker.io,开发代理
- DOMAIN-SUFFIX,docker.com,开发代理
- DOMAIN-SUFFIX,npmjs.org,开发代理
- DOMAIN-SUFFIX,pypi.org,开发代理
- DOMAIN-SUFFIX,pythonhosted.org,开发代理
- DOMAIN-SUFFIX,registry-1.docker.io,开发代理
- DOMAIN-SUFFIX,googleapis.com,开发代理
- DOMAIN-SUFFIX,openai.com,开发代理
- DOMAIN-SUFFIX,company.internal,DIRECT
- DOMAIN-SUFFIX,lan,DIRECT
- IP-CIDR,10.0.0.0/8,DIRECT,no-resolve
- IP-CIDR,172.16.0.0/12,DIRECT,no-resolve
- IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
- GEOIP,CN,国内直连
- MATCH,开发代理
规则顺序非常重要。Clash 按从上到下的顺序匹配,内网域名和私有地址必须放在通用规则之前,否则可能被后面的代理规则覆盖。对于 Docker Registry,还要注意实际连接可能使用多个域名,例如登录、令牌交换和镜像层下载并不一定全部发生在 docker.io。出现拉取镜像卡住时,应在 Dashboard 的连接页面查看真实目标域名,再补充精确规则。
SSH 通常不遵循 HTTP 代理环境变量。如果希望 Git over SSH 经过代理,需要单独配置 OpenSSH 的代理命令;如果只是访问公司 Git 服务器,则更建议将内网地址明确设为直连。npm、pip、Go Module 和 Docker CLI 则可以按项目需要选择显式代理或依赖 TUN 接管,避免同时设置多套代理造成循环转发。
export HTTP_PROXY=http://127.0.0.1:7890 export HTTPS_PROXY=http://127.0.0.1:7890 export ALL_PROXY=socks5://127.0.0.1:7890 npm config set proxy http://127.0.0.1:7890 npm config set https-proxy http://127.0.0.1:7890 git config --global http.proxy http://127.0.0.1:7890 git config --global https.proxy http://127.0.0.1:7890
显式代理只适合确认链路或处理不支持 TUN 的特殊程序。长期使用时,要记得区分宿主机地址与容器地址:容器内的 127.0.0.1 指向容器自己,不是宿主机。如果服务采用 bridge 网络,通常需要使用宿主机网关地址,例如 Docker 网桥的网关 IP,或者在支持的系统上使用 host.docker.internal。
用 Rule Provider 维护开发域名
开发工具的域名会持续变化,直接把所有规则写进主配置文件很快就会难以维护。可以把代理域名、直连域名和企业内网域名拆成独立的 Rule Provider,并设置合理的更新周期。对于内部规则,使用本地文件更安全;对于公开规则,使用 HTTP Provider 时应确认来源可信,并保留本地缓存。
rule-providers:
developer-proxy:
type: http
behavior: domain
url: https://example.com/rules/developer-proxy.txt
path: ./rules/developer-proxy.txt
interval: 86400
company-direct:
type: file
behavior: classical
path: ./rules/company-direct.yaml
rules:
- RULE-SET,company-direct,DIRECT
- RULE-SET,developer-proxy,开发代理
- MATCH,开发代理
behavior: domain 适合每行一个域名的列表,索引效率较高;behavior: classical 适合混合使用 DOMAIN、DOMAIN-SUFFIX、IP-CIDR 等规则。修改 Provider 后,可以通过 Dashboard 手动更新,也可以重启容器验证加载结果。若规则集下载失败,Clash 往往会继续使用旧缓存,因此排障时要同时检查 URL 可达性、文件权限和 YAML 缩进。
常见故障的定位顺序
透明代理问题不应一上来就修改大量规则。建议按照“容器网络—DNS—端口—路由—规则”的顺序逐层验证,每一步只确认一个事实。
- 确认容器是否有基础网络:在容器内执行
ip route、ping网关,使用curl -I https://example.com测试。不通时先检查 Docker bridge、防火墙和默认路由,不要先改 Clash 规则。 - 确认代理端口可达:从容器内测试宿主机的
7890或 DNS 端口。连接被拒绝通常是监听地址、端口映射或防火墙问题;连接超时则更像是路由或网络策略问题。 - 确认 DNS 路径:检查
/etc/resolv.conf,使用dig查询,并观察 Mihomo 日志。解析成功但打开网站失败,可能是 DNS 结果与代理出口不一致;解析失败则先处理 DNS,不要调整策略组。 - 确认 TUN 设备和权限:查看容器是否能访问
/dev/net/tun,检查日志中是否出现 permission denied、route add failed 或 device not found。 - 确认规则命中:打开 Dashboard 的 Connections,检查目标域名、命中规则和实际策略组。若请求走了 DIRECT,说明规则未匹配;若走了代理但仍超时,再检查节点、SNI 和远端 DNS。
| 现象 | 常见原因 | 优先检查 |
|---|---|---|
| 容器完全无法联网 | 默认路由、Docker 防火墙或网络模式错误 | ip route、网关连通性、Docker 日志 |
| 域名能解析但连接超时 | 规则走错策略、节点不可用或出口 DNS 异常 | Connections、策略组、节点测速 |
| 出现 DNS 泄漏 | 应用绕过 DNS 劫持,或 fake-ip 未启用 | resolv.conf、dns-hijack、fake-ip-filter |
| Docker 拉取镜像失败 | Registry 使用了未覆盖的令牌或 CDN 域名 | 实时连接中的真实域名与规则 |
| 内网服务打不开 | 私有网段被代理,或内部 DNS 被外部解析 | RFC1918 规则、公司域名和 DNS 排除项 |
修改配置后,先使用 Mihomo 的配置检查功能或查看启动日志,确认 YAML 没有缩进错误,再重载配置。排障期间将 log-level 临时设为 debug 可以获得更多信息,但不要长期保持,因为日志可能包含域名、连接信息和错误细节。确认问题后恢复为 info,并清理不必要的端口暴露。
上线前的安全与稳定性清单
一套可用的透明代理不仅要能访问外网,还要在重启、节点切换和网络变化后保持可预测。上线前建议完成以下检查:
- Clash 的控制接口只绑定到可信地址,并设置足够复杂的
secret。 - 代理端口不直接暴露到公网,Docker 发布端口时明确指定监听地址。
- 为
10.0.0.0/8、172.16.0.0/12、192.168.0.0/16等内部地址设置直连规则,并根据实际网络补充公司网段。 - 为 Docker、公司内网和局域网域名配置 DNS 排除项,避免 fake-ip 破坏服务发现。
- 开发代理、国内直连和内网直连使用不同策略组,避免一个节点故障影响全部业务。
- Rule Provider 使用固定来源和本地缓存,更新失败时仍能使用上一次有效规则。
- 不要在配置文件中提交真实订阅链接、UUID、密码和 API 密钥,使用权限受控的挂载文件或环境变量。
当 TUN、DNS 和规则分流三层都验证完成后,Docker 中的 Git、npm、pip、镜像拉取和各种开发依赖就能以统一方式工作。遇到问题时,先判断流量是否进入 Clash,再判断 DNS 是否正确,最后才分析节点和规则;按照这个顺序排查,通常比反复更换代理节点更快找到根因。