Docker networking is usually designed around direct outbound connectivity: a container sends traffic through its default bridge gateway, the Docker host performs NAT, and the destination is reached without any application-level proxy settings. That model becomes inconvenient when you need containers to use Clash or Mihomo transparently. Setting HTTP_PROXY and HTTPS_PROXY for every container works for some command-line tools, but it does not cover all TCP clients, package managers, sidecars, or third-party images. It can also leak traffic when an application ignores proxy variables.

This guide presents a practical Clash Docker transparent proxy setup for developers and DevOps engineers. The goal is to route selected Docker traffic through Clash without modifying each container. We will build the configuration in layers: Clash listener settings, Docker daemon behavior, container DNS, Linux forwarding, and iptables rules. The examples are designed for a Linux host running Docker Engine and Mihomo or a Clash-compatible core. Commands may require small adjustments for your distribution, firewall frontend, or network layout.

Important scope: transparent proxying is a host-level network change. Test it on a disposable machine first, keep a second terminal connected through a local console or SSH session, and record your existing firewall rules before applying changes. Incorrect NAT rules can interrupt Docker networking or route private services through the proxy.

How Docker Transparent Proxying Works

A transparent proxy does not ask applications to speak HTTP or SOCKS. Instead, the operating system intercepts a connection before it leaves the host, identifies the original destination, and redirects the connection to a local Clash listener. Clash then applies normal domain, IP, and rule-set logic. From the container's point of view, it still connects directly to github.com, a registry, or an API endpoint.

On a typical Docker bridge network, the packet path looks like this:

  1. A process inside a container opens a TCP connection to a destination such as registry-1.docker.io:443.
  2. The packet enters the Docker bridge, commonly named docker0, and reaches the host's network namespace.
  3. iptables or nftables performs a redirect or TPROXY interception before the packet is forwarded to the physical interface.
  4. Clash receives the connection through its transparent listener and restores the original destination.
  5. The Clash rule engine selects a proxy group or the DIRECT route.

There are two important consequences. First, Clash must be able to see the traffic in the host network namespace. A listener bound only to an isolated container interface will not automatically intercept packets from Docker bridge networks. Second, the interception rules must avoid capturing Clash's own outbound connections. If Clash's connection to a proxy server is redirected back into Clash, you create a loop that usually appears as repeated timeouts, high CPU usage, or a rapidly growing connection list.

Transparent proxying is different from configuring Docker's built-in proxy support. Docker daemon proxy settings affect image pulls, registry access, and daemon operations, but they do not automatically proxy arbitrary traffic generated by running containers. Conversely, iptables interception can cover running containers but does not necessarily fix a daemon that cannot reach a registry during docker pull. In many environments, the most reliable design uses both methods: daemon proxy configuration for image management and transparent interception for application traffic.

Traffic source Recommended method Reason
Docker daemon image pulls Daemon HTTP/HTTPS proxy The daemon may run outside the container network namespace and start before Docker networks exist.
TCP traffic from bridge containers iptables REDIRECT or TPROXY Applications need no proxy variables or special support.
UDP and complex protocols Mihomo TUN or carefully designed TPROXY Simple REDIRECT rules are primarily useful for TCP.
Host-generated traffic Separate host policy Docker bridge rules do not automatically capture processes running directly on the host.

Prepare Clash and Docker Networking

Before changing firewall rules, verify where Clash is running. The simplest architecture places Mihomo directly on the Docker host. Clash then listens on the host's loopback or LAN address, and Docker bridge traffic is redirected to that listener. Running Clash in another container is possible, but it requires host networking, additional capabilities, or a shared network namespace. For a first deployment, host installation is easier to troubleshoot.

Use a recent Mihomo build or another Clash-compatible core that supports the listener and DNS fields used by your configuration. Client interfaces such as Clash Verge Rev can manage the YAML, but the transparent proxy behavior ultimately depends on the core and the operating system. Always check the exact fields supported by your installed version; forks can differ in TUN, DNS, and inbound listener syntax.

The following baseline configuration enables a mixed port for normal clients, a redirection port for transparent TCP traffic, and a DNS service that can answer container queries. The addresses and proxy names are examples only.

Clash Transparent Proxy Baseline
mixed-port: 7890
redir-port: 7892
allow-lan: true
bind-address: "*"
mode: rule
log-level: info

dns:
  enable: true
  listen: 0.0.0.0:1053
  enhanced-mode: fake-ip
  fake-ip-range: 198.18.0.1/16
  nameserver:
    - https://1.1.1.1/dns-query
    - https://dns.google/dns-query
  fallback:
    - https://1.0.0.1/dns-query

proxies:
  - name: primary-node
    type: vmess
    server: proxy.example.com
    port: 443
    uuid: replace-with-your-uuid
    tls: true

proxy-groups:
  - name: Proxy
    type: select
    proxies:
      - primary-node
      - DIRECT

rules:
  - DOMAIN-SUFFIX,local,DIRECT
  - IP-CIDR,127.0.0.0/8,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
  - MATCH,Proxy

redir-port is the destination used by a simple TCP REDIRECT rule. It is not the same as mixed-port, which accepts HTTP and SOCKS client protocols. If you redirect raw TCP packets to a mixed port, Clash may interpret the payload incorrectly. The DNS listener is also separate: Docker containers need to reach it through the host gateway, not through the container's own loopback address.

If your Clash process runs inside a container, prefer host networking for the first implementation:

Docker Compose Host-Network Example
services:
  mihomo:
    image: metacubex/mihomo:latest
    network_mode: host
    restart: unless-stopped
    volumes:
      - ./config:/root/.config/mihomo
    cap_add:
      - NET_ADMIN
      - NET_RAW

Do not expose the external controller publicly. Bind it to 127.0.0.1:9090 and set a secret. If LAN access is necessary, restrict it with a firewall rule and use a strong authentication token. The transparent listener can be reachable from Docker bridge interfaces, while the administrative API should remain private.

Configure the Docker Daemon Proxy

Transparent interception does not solve every Docker operation. The Docker daemon performs image pulls, manifest requests, layer downloads, and authentication flows itself. On many Linux installations, the daemon is a systemd service with its own environment and does not inherit the shell variables from your user session. Configure its proxy explicitly if Docker Hub or a private registry cannot be reached directly.

For a systemd-managed Docker Engine, create a drop-in directory and add a service override. Use the host's reachable Clash address. If Clash runs on the host, 127.0.0.1 is usually correct for the daemon. If Docker is running inside a virtual machine or rootless environment, use the address visible from that environment instead.

Docker systemd Proxy Override
sudo mkdir -p /etc/systemd/system/docker.service.d

sudo tee /etc/systemd/system/docker.service.d/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,::1,*.local,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,registry.local"
EOF

sudo systemctl daemon-reload
sudo systemctl restart docker
sudo systemctl show --property=Environment docker

Use an HTTP URL for the proxy variables when pointing to Clash's mixed port. Many programs call this variable a SOCKS proxy only when the scheme is explicitly socks5://. Do not assume that an HTTP proxy variable can be replaced with a SOCKS URL without checking the client's behavior. For a registry that must remain direct, add its hostname to NO_PROXY. Include internal domains, service-discovery names, private address ranges, and your registry's exact hostname.

After restarting Docker, test daemon access separately from container access:

  1. Run docker info and confirm the daemon is healthy.
  2. Try docker pull alpine:latest or another small public image.
  3. Inspect journalctl -u docker -n 100 --no-pager if the pull fails.
  4. Check the Clash connection list to confirm that registry requests arrive at the expected listener.
Keep the two paths distinct: a successful docker pull proves that the daemon proxy works, not that application traffic from containers is intercepted. Test both the daemon and a running container before declaring the deployment complete.

Apply iptables Transparent Rules

The following example uses a dedicated NAT chain and the Clash redirection port. It captures TCP packets entering from docker0, skips private destinations and the Clash process, and redirects the remaining traffic to port 7892. The exact chain ordering can vary with Docker, UFW, firewalld, and nftables compatibility mode, so inspect the resulting rules rather than copying blindly.

Docker TCP REDIRECT Rules
sudo iptables -t nat -N CLASH_DOCKER 2>/dev/null || true
sudo iptables -t nat -F CLASH_DOCKER

sudo iptables -t nat -A CLASH_DOCKER -d 0.0.0.0/8 -j RETURN
sudo iptables -t nat -A CLASH_DOCKER -d 10.0.0.0/8 -j RETURN
sudo iptables -t nat -A CLASH_DOCKER -d 127.0.0.0/8 -j RETURN
sudo iptables -t nat -A CLASH_DOCKER -d 169.254.0.0/16 -j RETURN
sudo iptables -t nat -A CLASH_DOCKER -d 172.16.0.0/12 -j RETURN
sudo iptables -t nat -A CLASH_DOCKER -d 192.168.0.0/16 -j RETURN
sudo iptables -t nat -A CLASH_DOCKER -d 224.0.0.0/4 -j RETURN
sudo iptables -t nat -A CLASH_DOCKER -p tcp -j REDIRECT --to-ports 7892

sudo iptables -t nat -D PREROUTING -i docker0 -p tcp -j CLASH_DOCKER 2>/dev/null || true
sudo iptables -t nat -A PREROUTING -i docker0 -p tcp -j CLASH_DOCKER

sudo iptables -t nat -L CLASH_DOCKER -n -v
sudo iptables -t nat -L PREROUTING -n -v

The private-network exclusions are essential. Docker containers often need direct access to databases, internal APIs, service discovery, Kubernetes nodes, or the Docker host. Without exclusions, a request to 172.17.0.1 or a corporate RFC1918 address may be sent to an external proxy and fail. Adjust the list for your environment; for example, add a corporate 100.64.0.0/10 range if your network uses carrier-grade or overlay addressing.

Docker may create additional bridge interfaces such as br-xxxxxxxx for user-defined networks. A rule limited to -i docker0 will not capture traffic from those networks. You can add one rule per bridge, or use a broader interface match after confirming that it will not intercept unrelated traffic:

Inspect Docker Bridges
ip -br link
docker network ls
docker network inspect bridge

# Example for a user-defined bridge:
sudo iptables -t nat -A PREROUTING -i br-xxxxxxxx -p tcp -j CLASH_DOCKER

Some systems use nftables as the native firewall backend. The iptables command may still work through a compatibility layer, but the rules could be installed in a different table than expected. Verify counters with iptables -t nat -L -n -v and inspect nft list ruleset when behavior is inconsistent. If UFW or firewalld manages the host firewall, add a persistent, supported rule through that tool rather than allowing a service restart to delete the manual chain.

REDIRECT is a practical starting point for TCP, but it does not transparently handle every UDP flow. DNS, QUIC, WebRTC, and some discovery protocols can bypass this design. If you require UDP interception, evaluate Mihomo TUN mode or a TPROXY-based policy routing setup. TPROXY is more powerful but requires packet marks, an ip rule, a route table, and a Clash listener configured for TPROXY. Do not combine partial REDIRECT and TPROXY recipes without understanding which packets each path owns.

Configure Container DNS and Test the Path

DNS is often the reason a transparent proxy appears broken. A container may resolve a domain using Docker's embedded resolver, receive an address that does not match Clash's intended rule path, and then connect directly to that address. Alternatively, a container may be configured with 127.0.0.1 as its DNS server, which points to the container itself rather than the host. Set DNS deliberately.

Docker's default embedded DNS usually listens at 127.0.0.11 inside a container and forwards requests according to Docker's configuration. This can be acceptable for direct traffic, but it does not automatically mean that all DNS requests are processed by Clash. To use the Clash DNS listener, determine the gateway address visible from the container:

Find the Container Gateway
docker run --rm alpine sh -c 'ip route; cat /etc/resolv.conf'

# Typical bridge gateway output:
# default via 172.17.0.1 dev eth0

If Clash listens on all host addresses at port 1053, a container can use the bridge gateway and that port. For a single container or Compose service, specify it explicitly:

Compose DNS Configuration
services:
  worker:
    image: alpine:latest
    dns:
      - 172.17.0.1
    dns_opt:
      - timeout:2
      - attempts:2

Docker's dns field accepts an address but not a port in the usual Compose syntax. If your Clash DNS service uses a non-standard port such as 1053, Docker cannot always send conventional DNS queries to that port directly. In that case, keep Docker's resolver and use Clash's TUN or DNS redirection features, or run a small local DNS forwarder on port 53 that forwards to Clash. The correct choice depends on whether your core expects plain DNS, encrypted DNS, or fake-IP handling.

Now test from inside a temporary container. Start with DNS, then HTTPS, then a request that should be direct. Use images that contain the necessary tools, because minimal images may not include curl, dig, or iproute2.

  1. Run docker run --rm nicolaka/netshoot dig github.com to inspect DNS answers.
  2. Run docker run --rm curlimages/curl:latest -I https://github.com to test TCP and TLS.
  3. Run docker run --rm alpine:latest wget -S -O- https://example.com for a second client implementation.
  4. Watch Clash's logs and connection panel while each command runs.
  5. Check iptables counters; the CLASH_DOCKER packet and byte counts should increase.

A useful control test is to temporarily stop the interception chain and compare behavior, then restore it. Also test an internal address such as a local service or database. If public HTTPS works but internal services fail, your exclusions or NO_PROXY policy are incomplete. If internal services work but public HTTPS bypasses Clash, the container is probably using another bridge interface, the rule is in the wrong firewall hook, or the application is using UDP/QUIC rather than TCP.

Troubleshoot Leaks and Harden the Deployment

Transparent proxy problems are easier to diagnose when you separate DNS, routing, and application behavior. A connection timeout does not prove that the proxy node is unavailable. It may indicate that the packet never reached Clash, that Clash could not resolve the destination, or that a return route was lost after NAT.

  • No Clash connection appears: inspect the container's network interface, the PREROUTING interface match, and iptables counters. Confirm that the service uses TCP and that the destination is not excluded.
  • Clash receives the request but it times out: test the selected proxy node from the host, verify the node's DNS and TLS settings, and check whether the rule unexpectedly selects DIRECT.
  • Image pulls still fail: inspect the systemd drop-in, restart Docker after changing it, and verify the daemon's environment with systemctl show.
  • Private services stop working: add the relevant subnet to both Clash rules and firewall bypass rules. Do not broadly bypass all traffic merely to make one internal hostname work.
  • Only some clients fail: check for QUIC, UDP, IPv6, or applications that use their own DNS-over-HTTPS implementation.
  • Connections loop or CPU rises: exclude the Clash process's own traffic and proxy server IPs. A self-intercepting rule is a common cause.

IPv6 deserves special attention. An iptables rule for IPv4 does not intercept IPv6 traffic. If the host and containers have usable IPv6 connectivity, an application may prefer an AAAA record and bypass your IPv4 transparent path. Either configure equivalent IPv6 interception and rules, or deliberately disable IPv6 for the Docker networks while you validate the deployment. Do not assume that an IPv4-only test proves that all traffic is covered.

Persist the configuration only after testing. Save firewall rules with the persistence mechanism used by your distribution, and ensure the rules load after Docker creates its chains. Docker can rewrite portions of its NAT table during service startup. A custom chain referenced from the correct Docker hook is generally safer than editing Docker-managed chains directly.

For production hosts, use a narrow policy:

  • Intercept only the Docker bridge interfaces that need proxying.
  • Bypass loopback, Docker control ranges, private services, multicast, and link-local destinations.
  • Keep the Clash controller bound to localhost or protected by a firewall and secret.
  • Use rule providers for large domain lists rather than embedding hundreds of entries in the main YAML file.
  • Monitor Clash logs, Docker daemon logs, NAT counters, and connection latency.
  • Document how to disable the chain quickly during an incident.

There is no universal need to proxy every Docker packet. A selective policy is easier to maintain and avoids sending package mirrors, internal registries, monitoring endpoints, and databases through an unnecessary external route. Start with the destinations that need proxy access, then expand only when measurements show a real requirement.

Frequently Asked Questions

Do I still need proxy variables inside containers?

Not for TCP traffic successfully intercepted by the host. That is the main advantage of transparent proxying. However, proxy variables can still be useful for tools that need application-level proxy authentication, for traffic outside the intercepted Docker bridges, or for environments where firewall changes are not permitted. Avoid defining contradictory proxy variables and transparent rules without documenting which path should win.

Why does docker pull fail while curl inside a container works?

The Docker daemon and a running container are different traffic sources. The daemon may use the host network namespace and systemd environment, while the container uses a Docker bridge. Configure the daemon's HTTP and HTTPS proxy separately, restart Docker, and verify its environment. A successful container request does not configure the daemon automatically.

Can a REDIRECT rule proxy UDP and QUIC?

Usually not in the same way as TCP. REDIRECT is commonly used for TCP connection interception. QUIC uses UDP, and DNS or discovery protocols may also use UDP. Use Mihomo TUN mode or a complete TPROXY and policy-routing design when UDP coverage is required. You can also block or deprioritize QUIC for selected destinations so clients fall back to TCP, but that is a policy choice rather than a universal solution.

Should I use TUN instead of iptables?

TUN is often the better long-term option when you need broad host coverage, UDP support, fake-IP DNS integration, and fewer hand-written firewall rules. iptables REDIRECT remains attractive for a narrowly scoped Docker TCP policy because it is easy to understand and limits the blast radius. Choose based on the required protocols, operational permissions, and how much control you need over host versus container traffic.

Once the daemon proxy, DNS path, bridge interception, and bypass policy have been tested independently, Docker transparent proxying becomes predictable rather than mysterious. Keep the configuration minimal, verify every network namespace separately, and treat firewall rules as production code. When you need a supported Clash client and the latest setup resources, start with the platform package that matches your host and then apply the transparent rules in a controlled test environment.

Get Started

Take Full Control of Your Traffic with Clash

Available on Windows, macOS, Linux, Android, and iOS. Flexible rules, simple setup, ready to use.

Download Free View Setup Guide →