Docker networking often fails at the boundary between the host and the container. The host can browse through Clash normally, while docker pull, a package manager, or a build step inside a container still times out. The reason is simple: containers do not automatically inherit the host application's proxy behavior. A browser may use an HTTP proxy or a TUN interface, but Docker has its own daemon, network namespace, DNS path, and outbound connection logic.
When several containers need the same network policy, adding proxy variables to every image is the wrong abstraction. It creates configuration drift, exposes proxy credentials to build logs and environment inspection, and misses traffic generated by tools that do not honor HTTP_PROXY. A better design is to make Clash the host-level policy engine and route selected Docker traffic through it transparently. This article explains how to build that setup with Docker bridge networks, Clash or Mihomo redirection, selective rules, secure controller access, and a repeatable troubleshooting workflow.
Understand the Docker-to-Clash Network Model
Before editing a Clash configuration, map the traffic path. In the common Linux bridge setup, a container receives an address from a private subnet such as 172.17.0.0/16. Its default gateway is the Docker bridge on the host, usually 172.17.0.1. When the container connects to registry-1.docker.io, the packet leaves the container namespace, crosses the bridge, and is routed by the host.
That path is important because the Docker daemon and ordinary container processes are not necessarily the same traffic source. A command such as docker pull is usually executed by the Docker daemon, which may run as a system service outside your shell environment. By contrast, curl inside a container is generated by a process inside the container namespace. A reliable design must account for both paths.
| Traffic source | Typical network path | Configuration concern |
|---|---|---|
| Docker daemon | Host process to the registry | Daemon proxy settings or host-level transparent routing |
| Container process | Container namespace → Docker bridge → host | Forwarding, NAT, and Clash interception |
| Build step | Temporary build container → bridge | Must work without modifying every image |
| Host browser | Host process → Clash listener or TUN | Useful for comparison, but not proof that Docker is configured |
| Docker DNS query | Container resolver → Docker embedded DNS → upstream | DNS mode and domain-based rule matching |
There are two broad implementation patterns. The first is an explicit proxy: configure Docker and applications to use Clash's HTTP or SOCKS listener. It is easy to understand, but every client must support and receive the proxy settings. The second is transparent routing: Clash intercepts traffic based on the host's routing and firewall path, so applications can connect normally. Transparent routing is usually the better fit for a development machine with many images and tools.
Prepare Clash for Transparent Routing
For new deployments, Mihomo is generally the most capable Clash-compatible core because it supports TUN mode, enhanced DNS behavior, rule providers, and additional routing controls. The exact field names can vary slightly between Clash derivatives and client interfaces, so validate the generated configuration in the client you actually run. Clash Verge Rev and Mihomo-based clients commonly expose these features directly, while older clients may require manual YAML editing.
The following baseline enables a TUN interface, automatic route installation, and system-wide DNS interception. It is intentionally conservative: private networks remain direct, and the proxy group name is an example that must match a group defined elsewhere in your configuration.
mixed-port: 7890 allow-lan: false mode: rule tun: enable: true stack: system auto-route: true auto-detect-interface: true strict-route: true dns: enable: true listen: 0.0.0.0:1053 enhanced-mode: fake-ip fake-ip-range: 198.18.0.1/16 nameserver: - https://dns.google/dns-query - https://cloudflare-dns.com/dns-query fake-ip-filter: - +.lan - +.local - localhost.ptlogin2.qq.com rules: - DOMAIN-SUFFIX,lan,DIRECT - DOMAIN-SUFFIX,local,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 - MATCH,Proxy
auto-route installs routes so traffic can enter the TUN interface without application-specific proxy variables. auto-detect-interface helps Mihomo select the active physical interface, which is useful on laptops that move between Wi-Fi, Ethernet, and VPN adapters. strict-route reduces accidental leaks by making route handling more deterministic, but it can expose mistakes in local-network rules. If a printer, development server, or corporate subnet stops working, inspect the routing rules before disabling strict behavior globally.
DNS deserves special attention. Domain rules are most reliable when Clash can see the original hostname instead of only a resolved address. Fake-IP mode assigns a synthetic address and keeps the domain mapping internally, allowing Clash to apply rules such as DOMAIN-SUFFIX,github.com,Proxy. Docker's embedded DNS service may still answer container queries through 127.0.0.11, so a working host DNS configuration does not automatically prove that container DNS is being intercepted correctly.
Route Docker Traffic Through the Host
On Linux, transparent interception depends on the relationship between Docker's bridge, the host routing table, and the TUN implementation. The simplest path is to let Mihomo install system routes and then verify that the Docker bridge traffic is visible to the core. Start with a normal bridge network rather than host networking; host mode removes useful boundaries and makes it harder to tell which component is responsible for a failure.
Create a small test network and run a container with basic diagnostic tools:
docker network create \ --driver bridge \ --subnet 172.30.0.0/24 \ clash-dev-net docker run --rm -it \ --network clash-dev-net \ --name clash-net-test \ alpine:3.20 sh
Inside the container, test the layers separately. First inspect the default route and resolver:
ip route cat /etc/resolv.conf cat /etc/hosts
Then test a direct IP, a normal domain, and a domain that you intentionally route through the proxy. Alpine images may not include every utility, so install a diagnostic package temporarily if necessary:
apk add --no-cache curl bind-tools nslookup github.com curl -I --connect-timeout 10 https://github.com curl -I --connect-timeout 10 https://registry-1.docker.io
Do not use a successful ping as the main test. Many services block ICMP, and a ping only proves that one protocol reached one address. HTTPS tests exercise DNS, TCP, TLS, routing, and the remote service in a way that better matches package installation and image pulls.
If the container cannot reach anything, check forwarding and firewall policy first. Docker normally installs its own NAT and forwarding rules, but a host firewall, VPN client, or hardened Linux profile may set the forwarding policy to DROP. Confirm the bridge address and interface names with ip addr and
If direct traffic works but proxy-selected domains fail, inspect Clash's connections view or API dashboard while running curl. You should see the destination domain, the selected rule, and the proxy group. If no connection appears, the TUN route is not receiving Docker traffic, or the traffic is bypassing the expected interface. If the connection appears as an IP-only request, DNS interception may be incomplete and domain rules will not behave as expected.
Treat the Docker Daemon Separately
A frequent mistake is to test curl inside a container and assume that docker pull uses the same path. The Docker daemon is an independent process. Depending on the platform, it may be managed by systemd, Docker Desktop, or a virtualized Linux environment. A host-level TUN route can cover the daemon, but daemon-specific proxy configuration may still be necessary, especially when the daemon runs outside the namespace that Clash is intercepting.
For a Linux systemd installation, an explicit daemon proxy can be used as a fallback or as a deliberate design. Store credentials in a protected environment file rather than placing them in a public image or shell history:
sudo mkdir -p /etc/systemd/system/docker.service.d sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf > /dev/null <<'EOF' [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,.local,172.16.0.0/12,192.168.0.0/16" EOF sudo systemctl daemon-reload sudo systemctl restart docker sudo systemctl show --property=Environment docker
This method is explicit rather than transparent, so it should not be treated as a universal substitute for TUN routing. It also requires the listener to be reachable from the daemon's network context. On Docker Desktop, 127.0.0.1 may refer to a VM or desktop backend rather than the host process running Clash. In that environment, use the proxy settings exposed by Docker Desktop or a host address reachable from its Linux VM.
Design Selective Rules for Development
Transparent proxying does not mean sending every packet through a remote node. Developers usually need a split policy: public registries, Git hosting, and selected package mirrors use the proxy, while local services, private repositories, corporate networks, and nearby mirrors remain direct. Narrow rules make the setup faster and easier to audit.
| Destination | Recommended route | Reason |
|---|---|---|
| Docker Hub and public registries | Proxy when required | Image manifests and layers may be slow or unavailable on the local route |
| GitHub, GitLab, package indexes | Proxy selectively | Improves access without affecting internal Git services |
| Docker bridge subnets | DIRECT | Required for container-to-container communication |
| Host gateway and LAN services | DIRECT | Prevents development databases and dashboards from leaving the LAN |
| Unknown destinations | Policy dependent | Use a deliberate MATCH rule instead of accidental behavior |
Put specific rules before broad rules. A common mistake is placing MATCH,Proxy above private-network exceptions. Once a connection matches, later rules are not considered. Also remember that rule providers are easier to maintain than hundreds of manually edited entries. Keep the stable local exceptions in the main configuration and place frequently changing service lists in providers.
rules: # Container and local traffic stays on the local network - IP-CIDR,172.17.0.0/16,DIRECT,no-resolve - IP-CIDR,172.30.0.0/24,DIRECT,no-resolve - 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 - DOMAIN-SUFFIX,corp.example,DIRECT - DOMAIN-SUFFIX,github.com,Proxy - DOMAIN-SUFFIX,githubusercontent.com,Proxy - DOMAIN-SUFFIX,docker.io,Proxy - DOMAIN-SUFFIX,ghcr.io,Proxy - DOMAIN-SUFFIX,pypi.org,Proxy - DOMAIN-SUFFIX,npmjs.org,Proxy - MATCH,Proxy
Registry traffic can involve several hostnames. Docker Hub may contact authentication, API, registry, CDN, and object-storage domains during one pull. If only docker.io is proxied, the initial login may succeed while a layer download still times out. Use the Clash connection log to discover the actual destinations, then add precise rules rather than blindly proxying all IP addresses.
For reproducible builds, keep the routing policy in version control but do not commit secrets, private node URLs, or authentication tokens. Document the expected proxy group name, the Docker subnets, and the test commands. A new developer should be able to clone the configuration, start Clash, create the test network, and determine whether the host policy is working in a few minutes.
Secure the Setup and Troubleshoot by Layer
Transparent routing changes packet flow, but it does not remove the need for access control. Keep Clash's external controller bound to 127.0.0.1 unless remote administration is genuinely required. Never expose the controller without a strong secret. The controller can switch nodes, inspect connections, and sometimes alter configuration; treating it as a harmless monitoring port is a serious security mistake.
external-controller: 127.0.0.1:9090 secret: replace-with-a-long-random-secret
When a request fails, test from the bottom of the stack upward. This avoids changing five settings at once and losing the cause:
- Container interface: confirm the container has an address, a default route, and a valid gateway.
- Host forwarding: verify that the host accepts traffic from the Docker bridge and that firewall rules are not dropping forwarded packets.
- DNS resolution: compare
nslookupresults inside the container with the host. A DNS failure is not the same as a proxy failure. - Direct HTTPS: test a known reachable domain and an IP endpoint. This separates general connectivity from rule matching.
- Clash visibility: watch the connection log or dashboard while making the request. Check the matched rule and selected group.
- TLS and registry behavior: if Clash sees the request but it fails after connection, inspect certificate errors, SNI, authentication, and remote node health.
- Daemon context: test
docker pullindependently from containercurl. Confirm the Docker daemon environment or Desktop proxy configuration.
Common symptoms provide useful clues. If every container request fails, suspect forwarding, TUN routes, or a firewall. If direct domains work but GitHub fails, inspect domain rules and DNS mode. If DNS resolves but HTTPS hangs, check whether the resolved address is a fake IP that the traffic path does not return to Clash. If docker pull fails while an interactive container works, focus on the daemon or Docker Desktop backend. If only large layers fail, investigate MTU, HTTP/2 behavior, the selected node, and the registry CDN rather than immediately rewriting rules.
docker info, docker network inspect, the container's route table, DNS output, the exact failing hostname, and the Clash connection entry. These details make intermittent problems reproducible and reveal whether the failure is in Docker, DNS, routing, or the remote proxy.A host-level Clash policy gives development environments one consistent place to manage routing. It avoids rebuilding images merely to add proxy variables, covers temporary build containers, and lets you keep private services direct while sending only selected public traffic through a proxy. Start with a small bridge network, validate DNS and HTTPS independently, confirm that Clash can see the connection, and only then expand the policy to production-like Compose projects. Once the network path is understood, Docker pulls, package installs, Git operations, and automated builds become predictable rather than a collection of per-container workarounds.
Take Full Control of Your Traffic with Clash
Available on Windows, macOS, Linux, Android, and iOS. Flexible rules, simple setup, ready to use.