Why Docker Needs a Transparent Proxy
Docker pulls fail, npm install stalls, and requests to GitHub time out for a surprisingly simple reason: a proxy configured on the host is not automatically inherited by every container. A browser opened through Clash may work perfectly while docker pull, package managers, Git, or a build process inside a container still uses the host network path directly.
This happens because Docker creates its own network namespace and normally places containers behind a bridge such as docker0. The container has a private address, its own default route, and DNS settings supplied by Docker. Clash, meanwhile, usually listens on the host loopback address or a host LAN address. Unless the container is explicitly given a proxy endpoint, or the host redirects container traffic into Clash's transparent-routing interface, the two network paths remain separate.
There are three practical ways to connect Docker workloads with Clash:
- Application-level proxy variables: set
HTTP_PROXY,HTTPS_PROXY, andALL_PROXYfor tools that understand them. This is the simplest and easiest method to audit. - Transparent forwarding with iptables: redirect selected TCP traffic from the Docker bridge to Clash's redirection or TProxy listener. This is useful for legacy tools that do not support proxy variables.
- TUN-based routing: let Mihomo capture traffic through a virtual interface and apply normal Clash rules. This is often cleaner on a desktop host, but requires careful route and permission management.
Configuration Goal
Route only the developer traffic that needs Clash, keep local services and private networks DIRECT, prevent DNS leaks, and make every decision visible in logs.
1Understand the Traffic Path Before Editing Rules
A reliable setup begins with a map of the traffic path. Consider a container named builder connected to Docker's default bridge. Its traffic usually follows this sequence: the process asks the container resolver for a hostname, Docker forwards the DNS request according to its daemon configuration, the container sends packets through its default gateway, and the host performs network address translation before sending them to the upstream network.
If Clash is listening only on 127.0.0.1:7890, a container cannot reach that address. Inside the container, 127.0.0.1 means the container itself, not the host. A container can generally reach the host through the bridge gateway, commonly an address such as 172.17.0.1, but the exact value must be checked rather than assumed.
The first command shows the bridge subnet and gateway. The second confirms the container's default route. The third reveals which resolver Docker supplied. The final command checks whether Clash or Mihomo is listening on an address reachable from the bridge. If the listener is bound only to loopback, update the client setting to allow LAN access or bind the required listener to the host bridge address. Do not expose a proxy listener to the public internet.
Proxy Variables, Redirection, and TUN Compared
Proxy variables are usually the best starting point because they do not alter the host firewall. Docker Compose can provide them to one service, and the configuration is easy to remove or review. However, support differs: curl, Git, npm, Go, Python package tools, and many modern CLIs support proxy variables, while some native libraries, helper binaries, or custom TCP clients ignore them.
iptables redirection works below the application layer. It can capture ordinary TCP connections even when an application knows nothing about proxies. The trade-off is complexity: you must exclude Clash itself, the Docker gateway, private address ranges, and traffic that must remain local. UDP and some unusual protocols require additional support and should not be assumed to work merely because TCP works.
TUN mode provides a virtual network interface and can capture more traffic than a simple HTTP proxy. Mihomo then applies DNS and routing rules consistently. On Linux, TUN requires the correct kernel support and permissions. On Windows and macOS, the client may install a system service or network extension. TUN is powerful, but a bad route can capture the host's own control traffic and create loops, so test it after the basic proxy-variable path is working.
- Choose proxy variables for CI jobs, temporary builds, and teams that want configuration close to the service definition.
- Choose iptables redirection when an isolated Linux host must transparently handle legacy TCP applications.
- Choose TUN when the host needs unified rule-based routing for containers and desktop applications.
2Build a Developer-Focused Clash Configuration
Before touching Docker routing, make the Clash profile predictable. Use a dedicated policy group for developer traffic instead of sending every request through one global node. This makes it possible to choose a stable node for source repositories while keeping domestic registries, local networks, and private company endpoints DIRECT.
The following example uses Mihomo-compatible YAML. Replace the policy-group names with names that exist in your subscription. The example is intentionally conservative: DNS is handled by Clash, private domains bypass the proxy, and common developer services use a selectable proxy group.
There are several details worth understanding. allow-lan: true allows devices on permitted host interfaces to reach the listener; it does not by itself configure Docker or create firewall rules. bind-address: '*' makes the listener available on host interfaces, so use a firewall to restrict access and never forward the port from a router.
fake-ip can make rule matching consistent because Clash resolves names and associates them with an internal address. Some applications do not work well with fake IPs, especially software that expects a real DNS response or performs its own address validation. In those cases, add a carefully scoped fake-ip-filter entry or switch to redir-host for testing instead of disabling DNS handling globally.
Keep private DNS separate from public DNS. The nameserver-policy example sends internal names to an internal resolver, while public developer domains use the configured Clash resolvers. Do not route internal service names to a public resolver, and do not put private hostnames in a public subscription or shared configuration file.
Pro Tip: Start With Explicit Domains
Begin with a short list of domains visible in actual logs. Broad keyword rules can accidentally proxy mirrors, telemetry, local services, or authentication endpoints and make troubleshooting much harder.
3Hands-On Setup: Connect a Container to Clash
The safest first implementation is application-level proxying. Verify the host bridge gateway, make Clash reachable from that address, and pass the proxy variables only to the container that needs them. On a standard Linux Docker bridge, the host gateway is often 172.17.0.1, but Compose can provide a stable hostname through host-gateway.
- Confirm that Clash is listening on the host port used in your profile, such as
7890for a mixed HTTP and SOCKS listener. - Check the host firewall and permit access from the Docker bridge subnet only. Do not use a public interface for an unauthenticated listener.
- Test reachability from a temporary container before changing a production image.
Use a service-level definition so unrelated containers remain unchanged. The lowercase variables matter because different tools inspect different spellings.
Test in this order. Name resolution confirms that the container can obtain an address. Verbose HTTPS output shows whether the connection reaches the proxy and whether TLS negotiation completes. npm and Git have their own configuration layers, so environment variables may be overridden by values stored in .npmrc or Git configuration.
For a SOCKS-only setup, use a URL such as socks5h://host.docker.internal:7891 when the client supports it. The h is important because it requests proxy-side hostname resolution. A plain socks5:// may resolve the hostname inside the container first, which can create inconsistent results or a DNS leak.
Only move to transparent routing after this controlled path works. If a proxy variable setup fails, iptables or TUN will not magically solve an invalid listener, an unavailable node, a certificate problem, or an incorrect DNS policy.
4Transparent Routing With iptables or TUN
Transparent routing is useful when an application ignores environment variables. On a Linux host, Clash or Mihomo can expose a redirection listener, commonly called a redir-port, or a TProxy listener for more advanced traffic handling. The exact syntax differs between iptables and nftables, distributions, and Mihomo versions, so treat the following as a design pattern rather than a universal copy-and-paste firewall.
A redirect design generally performs four tasks: identify traffic originating from the Docker bridge, exclude destinations that must remain local, exclude the proxy process and its own outbound connections to prevent loops, and send eligible TCP traffic to Clash. The chain must also be placed at the correct point in the host's packet path, before Docker's forwarding and NAT rules make the original context difficult to match.
Do not apply this pattern without identifying the correct Docker subnet and listener. A wrong source range can capture host traffic; a wrong destination exclusion can break databases, service discovery, or internal APIs; and a missing owner exclusion can create an infinite loop. Persist firewall changes only after a reboot test and keep a recovery command available through a local console.
TUN Mode Considerations
TUN mode can be preferable when Mihomo is already managing the host's routes. Enable it through the client interface or profile only if your version supports the required settings. Typical considerations include auto-route, auto-detect-interface, DNS hijacking, and whether Docker's bridge interface should be included in the capture set.
On a desktop system, start with TUN enabled for the host, then create a test container and inspect its behavior. On a server, be more conservative: a route change can disconnect SSH, interrupt container image downloads, or redirect the control plane through a proxy that cannot reach private infrastructure. Use an out-of-band console or a staged deployment window.
- Keep Docker bridge traffic and TUN traffic clearly separated in the route table.
- Exclude loopback, RFC1918 private ranges, Docker service networks, and internal DNS addresses when they must remain local.
- Do not assume that transparent TCP capture handles UDP, ICMP, QUIC, or raw sockets.
- Use a dedicated policy group for captured developer domains and preserve DIRECT rules for internal resources.
5DNS Policy, Registry Behavior, and Rule Ordering
DNS is often the hidden cause of an apparently broken transparent proxy. A container may resolve registry-1.docker.io locally, receive an address that is slow or unreachable from the host, and then connect to that address without giving Clash enough hostname information for rule matching. Conversely, forcing every DNS request through a remote resolver may break internal names or introduce unnecessary latency.
Use a consistent policy. If fake IP mode is enabled, make sure the container's DNS path is actually intercepted by Mihomo. If the container bypasses Clash DNS, its resolver behavior may not match the rules you see in the dashboard. When using proxy variables, curl with an HTTP proxy may send the hostname to the proxy, while other tools may resolve first. Test each important tool instead of inferring behavior from one successful command.
Docker Hub is also a multi-endpoint workflow. A pull can involve the registry API, authentication endpoints, token services, CDN hosts, and image layers. A rule for only docker.io may be insufficient. Watch the Clash connection log during docker pull and add exact domains only when evidence shows they are required.
Common Pitfall
Do not place MATCH,Developer above private-network exclusions. Rule order is first-match based, so a broad final policy placed too early can proxy databases, Kubernetes APIs, and internal package mirrors.
For npm, remember that the package registry and downloaded tarballs may use different hostnames. For Python, PyPI metadata and files can be served from separate domains. Go modules may follow redirects and consult a checksum database. GitHub releases can involve asset hosts that differ from the repository page. A robust configuration therefore combines narrow domain rules with logs, not a long list copied from an unrelated environment.
- npm: inspect
npm config listand verify both registry and proxy settings. - Git: inspect
git config --global --get-regexp 'http.*proxy'and remove stale proxy values when switching modes. - Python: check
pip config list, environment variables, and any corporate certificate bundle. - Go: review
GOPROXY,GOSUMDB, and whether private module patterns belong inGONOSUMDB. - Docker: configure the Docker daemon separately when the failure occurs during image pulling rather than inside a running container.
6Systematic Troubleshooting With Logs
Change one layer at a time. First test a direct connection from the host. Next test the Clash listener from a temporary container using curl -x. Then test a real application with proxy variables. Only after those paths are understood should you enable transparent capture. This sequence tells you whether the failure is caused by the upstream node, host reachability, container DNS, application behavior, or firewall routing.
In the Clash dashboard, inspect the connection's domain, process or source metadata, matched rule, selected policy group, and final node. A request that never appears in the dashboard is not reaching Clash. A request that appears with the wrong rule indicates rule order or DNS behavior. A request that matches correctly but fails at the node points toward upstream connectivity, TLS, authentication, or IP reputation.
Comparing the proxied and --noproxy requests is particularly useful. If the proxied request fails while the direct request works, check the selected node, proxy protocol, and TLS negotiation. If both fail from the container but the host succeeds, check bridge egress, MTU, DNS, and firewall forwarding. If curl works but npm fails, inspect npm's own configuration and certificate trust store.
Certificate errors deserve careful attention. A normal HTTPS proxy using the CONNECT method should not require TLS interception inside the container. Do not disable certificate verification as a first response. Instead, confirm the container clock, CA package, corporate certificate requirements, and whether an environment variable points to an obsolete certificate file.
Finally, record a known-good baseline: the Clash profile hash or revision, listener ports, Docker subnet, DNS mode, selected node, and the exact test commands. When a subscription updates or a client changes from Clash Verge to Clash Verge Rev or Mihomo, this baseline makes regression analysis much faster.
Operational Tip
Use log-level: info while diagnosing, then lower verbosity after the route is stable. Avoid publishing logs that contain private hostnames, repository URLs, access tokens, or full query strings.
7Production Checklist and Maintenance
A developer proxy should be dependable without becoming an invisible single point of failure. Keep the configuration in version control, but store subscription URLs, credentials, and private resolver addresses outside public repositories. Use environment substitution or a secret manager for sensitive values, and document which settings are generated by the client rather than manually maintained.
- Confirm that the Clash listener is reachable only from trusted host and Docker interfaces.
- Define
NO_PROXYfor loopback, Docker service names, private networks, internal registries, and company domains. - Test both a proxied developer domain and a DIRECT internal domain after every profile update.
- Monitor node latency and failure rate instead of choosing a node solely by geographic label.
- Keep IPv6 behavior explicit. Disable it consistently during testing if the proxy path does not support IPv6.
- Test image pulls, npm installs, Git fetches, and container-to-container traffic separately.
- Persist firewall or TUN settings only after verifying reboot behavior and SSH or console recovery.
- Review logs for accidental routing of credentials, internal domains, and private APIs through external nodes.
The most maintainable design is usually selective rather than global: application-level proxy variables for controlled build services, explicit Clash rules for public developer platforms, DIRECT access for private infrastructure, and transparent routing only for applications that truly require it. Once the traffic path is visible and each layer has a test command, Docker and Clash stop behaving like two competing network stacks and become predictable parts of the same development environment.