Skip to main content
EN

Home / Blog / Deployment

Getting Docker and WSL2 onto the host's Clash proxy

Deployment2026-06-141330 words3 min read
Getting Docker and WSL2 onto the host's Clash proxy

Clash runs fine on the host, and then everything inside a container times out. The reason is simple: a container has its own network namespace, so 127.0.0.1 means the container itself.

The core prerequisite: enable allow-lan

Whatever approach you take, step one is the same — change the address Clash listens on from 127.0.0.1 to 0.0.0.0:

Turn on Allow LAN in Clash Verge, or write it in the config:

allow-lan: true
bind-address: "*"
mixed-port: 7897

Verify:

netstat -an | findstr 7897

Seeing 0.0.0.0:7897 means it worked. 127.0.0.1:7897 means it did not.

Docker: three separate scenarios

Most of the confusion comes from not realising these three need configuring separately.

Three scenarios, three configurations11. docker pullthe Docker daemon is the one on the network, so configure the daemon's proxy22. docker buildthe build container is on the network, so pass build-args33. docker runa process inside the container is on the network, so pass environment variables
Configuring one does not enable the other two — this is the single most common source of confusion

Scenario 1: docker pull will not fetch images

This is the Docker daemon on the network; containers are not involved.

Docker Desktop (Windows / macOS): Settings → Resources → Proxies → enable Manual proxy configuration and fill in:

HTTP:  http://127.0.0.1:7897
HTTPS: http://127.0.0.1:7897
Bypass: localhost,127.0.0.1,*.local

Docker Desktop's daemon runs in a VM but can reach the host loopback, so 127.0.0.1 is correct here.

Docker on Linux (systemd):

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:7897"
Environment="HTTPS_PROXY=http://127.0.0.1:7897"
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"
EOF

sudo systemctl daemon-reload
sudo systemctl restart docker

Verify:

sudo systemctl show --property=Environment docker

Scenario 2: dependency installs time out during docker build

Building happens inside a temporary container, so it has to be passed in explicitly:

docker build \
  --build-arg HTTP_PROXY=http://host.docker.internal:7897 \
  --build-arg HTTPS_PROXY=http://host.docker.internal:7897 \
  --build-arg NO_PROXY=localhost,127.0.0.1 \
  -t myimage .

You do not need to declare these ARGs in the Dockerfile — Docker has built-in support for those particular names.

Scenario 3: the running container needs the network

docker run -it \
  -e HTTP_PROXY=http://host.docker.internal:7897 \
  -e HTTPS_PROXY=http://host.docker.internal:7897 \
  -e NO_PROXY=localhost,127.0.0.1,::1 \
  --add-host=host.docker.internal:host-gateway \
  ubuntu bash

In docker-compose:

services:
  app:
    image: myimage
    environment:
      - HTTP_PROXY=http://host.docker.internal:7897
      - HTTPS_PROXY=http://host.docker.internal:7897
      - NO_PROXY=localhost,127.0.0.1,::1,app,db
    extra_hosts:
      - "host.docker.internal:host-gateway"

Using the host's LAN IP instead of host.docker.internal

If host.docker.internal is unavailable, use the host's LAN address directly:

# on Windows
ipconfig
# on Linux, the docker0 bridge address (the host as seen from a container)
ip addr show docker0    # usually 172.17.0.1

Then substitute that address for host.docker.internal.

The easier route: enable TUN

That is a fair amount of configuration. If you would rather not, turn on TUN mode — all traffic is intercepted at the network layer and neither the Docker daemon nor any container needs configuring.

Two approachesConfigure each proxy explicitlyprecise and controllabledoes not affect other trafficthree places to configure, easy to miss oneTUN modeset once and forgetDocker is entirely unawareneeds virtual adapter privileges and can conflict with container networking
TUN is the low-effort choice on a development machine; on a server, configuring each piece is preferable

Under TUN, remember to add Docker's subnets to your direct rules so container-to-container traffic is not proxied:

prepend-rules:
  - IP-CIDR,172.17.0.0/16,DIRECT,no-resolve
  - IP-CIDR,172.18.0.0/16,DIRECT,no-resolve
  - IP-CIDR,10.0.0.0/8,DIRECT,no-resolve

WSL2: two networking modes

From a certain version, WSL2 supports mirrored networking, and the two modes are configured entirely differently. Confirm which one you are on first.

# run inside WSL2
cat /etc/resolv.conf
ip route show default

Mode 1: NAT (the default, traditional mode)

WSL2 has its own virtual adapter, and the host appears to it as a gateway address.

# derive the host IP and set the proxy
export HOST_IP=$(ip route show default | awk '{print $3}')
export HTTP_PROXY="http://$HOST_IP:7897"
export HTTPS_PROXY="http://$HOST_IP:7897"
export ALL_PROXY="socks5://$HOST_IP:7897"
export NO_PROXY="localhost,127.0.0.1,::1,*.local"

Put it in ~/.bashrc or ~/.zshrc as a pair of switches:

proxyon() {
  local host_ip=$(ip route show default | awk '{print $3}')
  export HTTP_PROXY="http://${host_ip}:7897"
  export HTTPS_PROXY="http://${host_ip}:7897"
  export ALL_PROXY="socks5://${host_ip}:7897"
  export NO_PROXY="localhost,127.0.0.1,::1,*.local"
  echo "proxy on → ${host_ip}:7897"
}
proxyoff() {
  unset HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY
  echo "proxy off"
}

Why derive the IP dynamically: WSL2's virtual adapter address can change every time the host reboots, so a hard-coded value stops working.

Mode 2: mirrored networking (newer Windows)

Enable it in %USERPROFILE%\.wslconfig:

[wsl2]
networkingMode=mirrored
dnsTunneling=true
autoProxy=true
firewall=true

Run wsl --shutdown afterwards to restart WSL.

In mirrored mode WSL2 shares the host's network namespace, so 127.0.0.1 works directly:

export HTTP_PROXY="http://127.0.0.1:7897"
export HTTPS_PROXY="http://127.0.0.1:7897"

autoProxy=true additionally makes WSL inherit the Windows system proxy setting, so in many cases you need no environment variables at all.

The two WSL2 networking modesNAT modeits own virtual the host IP has to be derived dynamicallyneeds a firewall exceptionbroadly compatibleMirrored modeshares the host'127.0.0.1 works directlycan inherit the system proxy automaticallyneeds a newer Windows
Use mirrored mode where you can — it removes a great deal of configuration

Verifying the proxy works

# check the environment variables
env | grep -i proxy

# try it
curl -I https://www.google.com

# check the exit IP (should be the node's)
curl https://api.ipify.org

If curl works but apt and pip still do not, those tools have their own proxy configuration; see the developer proxy cheat sheet.

Diagnostic checklist

When it will not connect, check in orderIs allow-lan enabled in Clash (netstat should show 0.0.0.0)Has the Windows firewall been openedCan the container or WSL ping the host IPDoes the port match the mixed port in ClashDoes NO_PROXY exclude internal service names and private rangesHave you configured only one of Docker's three scenarios (daemon

One quick check, run inside the container or WSL:

curl -v --connect-timeout 5 http://host-ip:7897

Connecting at all — even with a 400 response — means the network layer is fine; Connection refused points at allow-lan or the firewall.

In short

  • Step one is always allow-lan plus a firewall exception
  • Docker's three scenarios each need their own configuration: daemon, build, run
  • host.docker.internal needs --add-host on Linux
  • For WSL2, first identify the networking mode: NAT needs the gateway IP derived dynamically, mirrored mode takes 127.0.0.1
  • Do not leave private ranges and service names out of NO_PROXY
  • If it is all too much, enable TUN — but add Docker's subnets to your direct rules

Related: the developer proxy cheat sheet and TUN internals.


Related docs