Start by defining which Docker traffic needs proxying
“Proxy Docker” can describe several different traffic paths, and each path requires a different solution. A shell command such as HTTP_PROXY=http://127.0.0.1:7890 docker pull image:tag only affects a particular command-line client, and it may not affect the Docker daemon that actually downloads image layers. A proxy variable inside a container affects processes in that container, but it does not automatically affect the host, other containers, or Docker’s embedded DNS.
For a reliable setup, first identify the traffic source. Image pulls and pushes are normally performed by dockerd; package installation is performed by processes inside a container; and application traffic may originate from a Docker bridge, a user-defined network, or host networking. A transparent proxy should therefore be designed around the network path instead of around individual commands.
| Traffic | Typical process | Best starting method | Important limitation |
|---|---|---|---|
| Pulling images | dockerd on the host |
Docker daemon proxy or host-level routing | Container environment variables do not control it |
| APT, APK, npm, pip, or Git inside a container | Process in the container namespace | Host TUN transparent routing or explicit proxy variables | 127.0.0.1 means the container itself |
| Published service connections | Host forwarding and container process | Selective rules and route testing | Inbound and outbound paths are different |
| BuildKit or remote builders | BuildKit worker or remote daemon | Configure the active builder separately | Local Docker settings may not reach a remote builder |
The rest of this guide uses a Linux host with Docker Engine and a Clash-compatible kernel such as mihomo. The same design can be adapted to a graphical client, but the client must expose the kernel’s TUN mode, DNS behavior, and routing settings. TUN is not a magic switch: it creates a virtual interface and routing rules, while Docker adds network namespaces, virtual Ethernet pairs, bridges, forwarding, and NAT.
Choose between daemon proxy, explicit proxy, and transparent TUN
There are three practical approaches. The first is an explicit proxy. You configure Docker or the application with an HTTP or SOCKS endpoint, usually through a local address reachable from the relevant namespace. This is easy to inspect and often the safest option for image registries, but every application must support proxy configuration and HTTPS proxy semantics correctly.
The second is a Docker daemon proxy. This targets image pulls, registry authentication, and layer downloads performed by dockerd. On a systemd host, the configuration is commonly placed in a drop-in such as /etc/systemd/system/docker.service.d/http-proxy.conf. The exact endpoint must be reachable by the daemon, and the daemon must be restarted after the drop-in changes.
[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,::1,registry.example.internal,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
After changing a systemd drop-in, reload the unit and restart Docker:
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo systemctl show --property=Environment docker
This method does not transparently proxy arbitrary traffic from containers. It also does not necessarily configure BuildKit workers, rootless Docker, or a Docker daemon running inside another container. Treat it as a targeted solution for daemon traffic, not as a replacement for host routing.
The third method is a host-level mihomo TUN interface. It is useful when applications ignore proxy variables, when package managers differ, or when many containers should follow the same rule-based policy. TUN captures IP traffic at the host routing layer and sends selected connections through the mihomo core. With correct forwarding and DNS behavior, traffic leaving ordinary Docker bridge networks can be handled without adding proxy variables to every image.
| Method | Strength | Weakness | Use it when |
|---|---|---|---|
| Explicit HTTP or SOCKS proxy | Easy to verify and narrow in scope | Requires application support and namespace-aware addressing | Only a few tools need access |
| Docker daemon proxy | Directly covers image pulls and pushes | Does not cover arbitrary container processes | Registry access is the main requirement |
| mihomo TUN | Works for software that ignores proxy variables | Requires routing, DNS, and firewall validation | Many containers need consistent transparent routing |
Build a mihomo TUN configuration that does not create a loop
Start with a minimal configuration and expand it only after the host can reach the proxy. The following example illustrates the important fields rather than serving as a universal drop-in file. The actual outbound names, DNS servers, and rule providers must match the environment.
mixed-port: 7890
allow-lan: true
bind-address: '*'
mode: rule
log-level: info
external-controller: 127.0.0.1:9090
secret: replace-with-a-long-secret
tun:
enable: true
stack: system
device: mihomo
auto-route: true
auto-detect-interface: true
strict-route: true
dns-hijack:
- any:53
- tcp://any:53
dns:
enable: true
enhanced-mode: fake-ip
listen: 0.0.0.0:1053
nameserver:
- https://1.1.1.1/dns-query
- https://dns.google/dns-query
fake-ip-filter:
- '*.lan'
- '*.local'
- 'localhost.ptlogin2.qq.com'
rules:
- DOMAIN-SUFFIX,example.internal,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
The exact TUN field names can vary with the mihomo version and client integration, so validate the configuration in the client before enabling it on a production host. auto-route installs routes for the virtual interface, while strict-route reduces accidental fallback outside the intended path. auto-detect-interface helps select the physical uplink, but a machine with multiple NICs, VPN interfaces, or policy routing may require an explicit interface.
The most important anti-loop rule is to keep the mihomo process, controller, and upstream path reachable without sending them back into the same TUN interface. If the proxy node is reached through an address that is itself routed into TUN, the core may repeatedly intercept its own connection. Exclude the physical gateway, local management networks, and any required upstream interface according to the host’s routing design. Do not blindly exclude all private addresses if the proxy endpoint is inside a private network.
Keep Docker subnets and local services intentional
Docker commonly creates networks in private ranges such as 172.17.0.0/16, but the exact subnet depends on existing networks and daemon settings. Inspect the real values rather than assuming them:
docker network inspect bridge \
--format '{{json .IPAM.Config}}'
ip -4 addr show docker0
ip route
ip rule
Traffic to a local database, reverse proxy, registry mirror, or service discovery domain usually should remain direct. Add narrow DOMAIN, DOMAIN-SUFFIX, or IP-CIDR rules for those destinations. A broad private-range exclusion may be convenient, but it can bypass a proxy that is intentionally used to reach a private corporate network. Rule order matters: mihomo evaluates rules from top to bottom, so a later proxy rule cannot override an earlier DIRECT match.
Fake-IP DNS also deserves a deliberate decision. It can provide stable domain-based rule matching, but some Docker workloads expect ordinary A or AAAA answers, perform their own DNS validation, or communicate with software that does not tolerate synthetic addresses. If a container shows valid DNS replies but cannot connect to the returned address, test redir-host mode or add a narrowly scoped fake-IP exclusion. Avoid changing DNS mode and routing rules at the same time; otherwise the source of a failure becomes difficult to isolate.
Connect Docker bridge traffic to the host TUN path
A host TUN interface does not automatically mean that every Docker namespace will be captured. A container sends packets through its default route, commonly the Docker bridge gateway. The packet then crosses the host’s forwarding path and may be subjected to Docker’s iptables or nftables chains and NAT rules. Whether mihomo sees the traffic depends on the TUN implementation, route installation, forwarding policy, and the firewall framework in use.
First verify the basic path without involving an application:
docker run --rm alpine:3.20 ip route
docker run --rm alpine:3.20 cat /etc/resolv.conf
docker run --rm alpine:3.20 ping -c 1 172.17.0.1
docker run --rm curlimages/curl:8.10.1 \
-I --max-time 15 https://example.com
The first command should show a default route through the container’s Docker gateway. The resolver file may show Docker’s embedded DNS address, commonly 127.0.0.11. That address is not the host’s loopback; it is a Docker-provided resolver inside the container namespace. Docker forwards DNS requests according to the daemon and host resolver configuration, so a host TUN DNS hijack may not be sufficient by itself.
Enable forwarding only as required by the host’s network policy, then check the effective firewall state. Do not paste rules from an unrelated distribution into a production server. Docker may use iptables-nft compatibility, native nftables, or a client-managed firewall, and inserting a broad redirect rule into the wrong chain can break published ports and container-to-container traffic.
sysctl net.ipv4.ip_forward
sudo iptables -S FORWARD
sudo iptables -t nat -S
sudo nft list ruleset
For a TUN-based design, prefer mihomo’s supported automatic route and DNS mechanisms before adding custom REDIRECT or TPROXY rules. TUN and TPROXY are not interchangeable. TUN captures traffic through a virtual layer-3 interface; TPROXY commonly requires socket marks, policy routing, and firewall rules that preserve the original destination. Combining an automatic TUN route with an improvised transparent redirect can produce duplicate interception, broken UDP, or a loop through the proxy port.
Some environments need an explicit exclusion or route for the Docker bridge itself. Other environments require the container subnet to be included in the host policy-routing table so packets from docker0 enter the TUN path. The correct command depends on whether the kernel uses main-table routes, fwmarks, nftables, or a client-managed service. Confirm the result with packet counters and logs instead of assuming that a route exists because the TUN interface is visible.
Use a host gateway only for explicit proxy mode
If you choose an explicit HTTP proxy for containers, expose the host endpoint in a controlled way. On Linux, the proxy must listen on an address reachable from the Docker bridge, not only on 127.0.0.1. In a Docker Compose file, a host-gateway mapping can provide a stable name on supported Docker versions:
services:
worker:
image: debian:bookworm
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,host.docker.internal,.internal
Only use allow-lan: true and bind-address: '*' when the host firewall restricts access to the Docker bridge or trusted networks. An unauthenticated mixed port exposed on a public interface can become an open proxy. Prefer a firewall rule limited to the Docker subnet, and keep the external controller bound to 127.0.0.1 or another protected management address.
Configure registry and package traffic without breaking local access
Image registries often involve more than one hostname. A Docker client may contact an authentication endpoint, resolve a registry hostname, request a token, and then download layers from a content host or redirect target. Proxying only the visible registry URL may therefore produce an authentication success followed by a layer download timeout.
Test the daemon path independently from a container path:
docker info
docker pull alpine:3.20
docker pull registry.example.com/team/base:latest
docker system info --format '{{json .Driver}}'
Check the Docker daemon logs while testing. On a systemd host, use journalctl -u docker -f. If the daemon proxy is configured, confirm that the environment is present in the running service rather than only in the shell that launched the command. A successful curl from the host proves only that the host process can connect; it does not prove that dockerd has the same proxy, DNS, credentials, or route.
For package managers inside containers, test both DNS and HTTPS. A Debian container might need apt-get update, while an Alpine image uses apk update; a minimal image may not include curl, ip, or certificate authorities. Install diagnostic tools temporarily or use a dedicated troubleshooting image rather than modifying the production image merely to debug networking.
docker run --rm debian:bookworm \
sh -c 'apt-get update && apt-get install -y --no-install-recommends ca-certificates curl'
docker run --rm alpine:3.20 \
sh -c 'apk add --no-cache ca-certificates curl && curl -Iv https://dl-cdn.alpinelinux.org'
Do not confuse TLS certificate failures with proxy routing failures. An HTTPS connection may reach the server through mihomo and still fail because the container lacks a current CA bundle, the system clock is incorrect, or an enterprise TLS inspection certificate has not been installed. Conversely, disabling certificate verification can hide the actual problem and should not be used as a permanent fix.
Define NO_PROXY carefully. Include loopback, Docker’s gateway, internal registry names, service discovery domains, and private destinations that must remain direct. Avoid placing every domain in NO_PROXY just to make one internal service work; that can silently send package and registry traffic outside the intended policy. For CIDR matching, confirm that the specific HTTP client understands CIDR entries. Some tools accept IP ranges while others match only hostnames or suffixes.
Debug DNS, rules, connectivity, and throughput in order
Debug one layer at a time. Begin with the host, then the Docker namespace, then the application protocol. Changing the proxy node, DNS mode, firewall rules, and container image simultaneously makes the result difficult to interpret.
- Confirm the core is alive: Check that mihomo is running, the mixed port is listening, the TUN device exists, and the client log does not report a failed permission or route operation.
- Confirm the host path: Test the same registry or package URL from the host with
curl -v. Verify whether the request is matched byDIRECTor a proxy policy. - Confirm the container route: Inspect the default gateway, interface address, resolver file, and connectivity to the Docker bridge gateway.
- Confirm DNS separately: Compare name resolution with a known IP test. A DNS timeout and an HTTPS timeout are different failures.
- Confirm rule matching: Use mihomo’s connection view or logs to see the destination, matched rule, selected policy, and outbound failure reason.
- Confirm the protocol: Test the registry’s HTTPS endpoint, authentication endpoint, and redirected content host rather than only the first hostname.
- Measure throughput last: Once correctness is established, compare layer download speed, concurrent connections, MTU, and the selected proxy node.
Useful host-side checks include:
ss -lntup | grep -E '7890|9090|1053'
ip link show mihomo
ip route get 1.1.1.1
curl -v --connect-timeout 10 https://registry-1.docker.io/v2/
docker run --rm busybox:1.36 nslookup registry-1.docker.io
If the host works but the container fails, inspect forwarding, Docker DNS, and the container’s route before replacing the proxy node. If both fail, inspect mihomo’s outbound connection and rule selection. If DNS works but TCP fails, check firewall policy, MTU, and whether the destination is being sent to the correct outbound. If TCP connects but HTTPS fails during the handshake, check certificates, SNI-related behavior, time synchronization, and possible TLS interception.
Throughput can be lower with TUN than with a direct daemon proxy because packets pass through additional routing, DNS interception, policy matching, and sometimes a userspace stack. Large image pulls may also use multiple concurrent layer connections, so a latency test for one proxy node does not predict registry performance. Compare a single small image, a multi-layer image, and a package download. Watch CPU usage and connection logs while testing.
Before production use, test a restart of both Docker and the Clash-compatible client. Confirm that the TUN device, routes, DNS behavior, and daemon proxy environment return correctly after boot. Record the active mixed port, Docker bridge subnet, internal registry exclusions, and the command used to disable TUN. This turns a fragile workaround into a repeatable network configuration that can be diagnosed when a proxy node, kernel, Docker version, or host firewall changes.