Why a Working Clash Session Does Not Fix docker pull by Itself
Clash on the desktop usually proves itself with a few HTTPS tabs, yet docker pull and docker build can still stall for minutes or fail with read timeouts. The emotional mismatch is real: you already turned on a proxy you trust, so it feels as if the entire machine should benefit. The missing detail is that the Docker Engine is a long-running daemon. It fetches container images and layers from a registry on its own network path, which is separate from Chrome or Safari using the “system HTTP proxy” toggles, and it is also different from a single process inside a shell where you hand-exported HTTPS_PROXY yesterday. Until you wire the daemon, BuildKit, and sometimes each build stage consistently, the engine is still trying to talk to registry-1.docker.io or a mirror the hard way, across whatever middleboxes your uplink has.
Another common trap is loopback. Inside most Linux container networks, 127.0.0.1 is the container itself, not your laptop, and on Docker Desktop the VM that hosts the engine has its own idea of localhost too. If you set HTTP_PROXY=http://127.0.0.1:7890 in the wrong context, you may be pointing at nothing that listens, even while Clash is healthy on the host. The address you need is the host’s reachable IP or the special hostnames Docker documents for this job, not blind localhost paste from a web tutorial. For Windows users who already solved the sibling problem for WSL2, our WSL2 and Windows Clash guide is the right parallel read; this page focuses on the OCI path instead of apt and git.
NO_PROXY for private registries.
Clash: Mixed Port, Bind Address, and “Allow LAN”
Meta-based clients expose a mixed port that speaks HTTP and SOCKS on one listener; many configs default near 7890, but your YAML is authoritative. For docker pull you typically want an HTTP proxy URL such as http://<host>:7890 unless you standardize on SOCKS in both Clash and Docker. The second axis is as important as the port: if Clash binds only to 127.0.0.1, the Docker daemon—which often arrives from a docker0 bridge or a Linux VM—cannot open that socket. You need a binding that the engine can actually reach, such as 0.0.0.0 on the host interface or an explicit “accept connections from the LAN” option that GUI apps label Allow LAN or similar.
That wider binding is not a free pass: it increases exposure on all interfaces that share the same scope, which is why pairing it with a sane host firewall and a network you trust still matters. If you are intentionally sharing the same Clash port with phones, compare notes with LAN sharing and Wi-Fi boundaries; for Docker the “LAN” is often your own machine, but the packet still crosses an edge. After changes, verify from the host with ss -lntp or Windows netstat that the port is in listen state on the address you expect, not on loopback alone.
Resolving the Host from Containers and the Engine
On Docker Desktop for Windows and macOS, special DNS names exist so processes can refer to the physical machine without hand-copying a DHCP address. host.docker.internal is the usual bridge from container user space to host services, and the Desktop documentation for your exact version is the only contract that matters, because product teams tune behavior across releases. On native Linux with the stock bridge, a common pattern is the bridge gateway IP, often the first address on docker0, for example 172.17.0.1 on a default install, but you should not memorize one number: run ip addr show docker0 or docker network inspect bridge and read the actual gateway. Corporate VPNs and rootless modes can change this picture; treat any blog constant as a guess until your shell agrees.
When you have both WSL2 and Docker in play, the Windows host address you learned for apt is not always identical to the address the Linux VM of Docker Desktop should use, because those stacks are not the same process boundary. If something works in a WSL shell but not in docker run, do not merge the debugging stories without new measurements. A cheap universal check is to run a tiny throwaway container with curl or wget and try HTTP_PROXY toward your Clash port explicitly before you trust global daemon config.
Configuring the Docker Engine to Use the Host Proxy
On Linux with systemd, the approach Docker documents most often is a drop-in for the docker service, not a shell export. Create a file such as /etc/systemd/system/docker.service.d/http-proxy.conf (your paths may differ slightly by distro) with:
[Service]
Environment="HTTP_PROXY=http://172.17.0.1:7890"
Environment="HTTPS_PROXY=http://172.17.0.1:7890"
Environment="NO_PROXY=localhost,127.0.0.1,::1,.internal,.svc,.cluster.local"
Replace the IP with the bridge gateway you measured, extend NO_PROXY for any on-prem registry hostnames that must stay direct, then run systemctl daemon-reload and systemctl restart docker. Confirm with systemctl show docker --property=Environment or a small docker pull test. If your packager prefers an EnvironmentFile= or a daemon.json hook instead, use their layout; the key idea is that the engine process inherits the HTTP proxy, not that you only set variables in an interactive shell.
On Docker Desktop, the settings UI exposes proxy fields for the Linux VM that backs the engine. Use the same host.docker.internal:7890 style your version documents, turn manual proxy mode on if the UI distinguishes automatic versus manual, and apply changes so the VM restarts. The Desktop registry mirror and acceleration fields are separate: they point the engine to an alternate mirror endpoint, which is popular in networks where public registries are slow, while HTTP_PROXY is how a client reaches the Internet through a forward proxy. You can use both, but you should understand which hop each setting affects so you are not double-proxying or pointing the mirror at a path your rules block.
BuildKit, docker build, and Environment in Build Stages
When you run docker build, modern BuildKit can fetch external assets during a Dockerfile step. The proxy story splits into “what the daemon needs for base layers” and “what a RUN line needs for apt, curl, or language package managers.” The engine-level container proxy settings do not always propagate into every RUN the way new users assume. The robust pattern is to pass build arguments such as HTTP_PROXY and HTTPS_PROXY with --build-arg, and to declare ARG lines in the Dockerfile so the values are in scope, then rely on the same NO_PROXY list for internal artifact servers.
Some organizations bake a two-tier story: a generic base image with no secrets, and a private CI layer that injects HTTP_PROXY at build time only. If you are debugging alone on a laptop, a simpler first step is a shell wrapper that exports the variables for both the client and the build subcommand, then narrows the failure to one Dockerfile line with verbose logs. If you need kernel-capture for binaries that completely ignore HTTP env, the heavier hammer is TUN; compare with Clash TUN mode and system capture after you are sure the proxy path is not a plain mis-typed address.
Why NO_PROXY Matters for Private and Air-Gapped Registries
A broad catch-all HTTP proxy is convenient until it steers traffic meant for a datacenter registry through an exit node on another continent. Symptoms include slow pushes, spurious authentication failures, and certificate errors when a forward proxy rewrites TLS. Before you file a ticket with IT, set NO_PROXY (and the lowercase no_proxy for picky tools) to include your private suffixes, loopback, link-local, and any Kubernetes in-cluster service DNS patterns you use. Clash can still be the right tool for public layers while direct paths stay direct for the subnet where your images already live. If policy requires inspection of private TLS, that is a different architecture than a single mixed port on a laptop; say so in runbooks, do not smuggle the requirement through a silent global proxy string.
A Practical Verification Ladder from Host to Container
Start on the host: can curl -v through --proxy http://127.0.0.1:7890 reach a URL your Clash profile allows? If that fails, fix Clash first. Next, with Allow LAN or equivalent, repeat using the LAN-facing address and port you intend Docker to use. Then enter a container with a shell and try the same with HTTP_PROXY set to the host or gateway form you chose. If only the first hop works, you still have a bind address or firewall gap. Windows Defender and third-party products can block synthetic interfaces even when a browser is untouched; a temporary narrow allow rule for the Clash binary or the destination port, scoped to the right profile, is a fair diagnostic. Log readers should skim Clash logs and rule hits to see whether a denied rule or a handshake timeout matches the docker pull time window, instead of guessing from UI icons alone.
Registry Mirrors, Acceleration, and When They Are Not the Same as HTTP_PROXY
Searchers often look for Docker Desktop mirror and acceleration keywords when a region’s path to a global registry is congested. A mirror is an alternate registry front that serves compatible layer metadata; it does not replace a forward proxy if your uplink to that mirror is still blocked, and it is not a substitute for reading corporate policies about allowed endpoints. A sensible 2026 setup might combine: (1) a working Clash HTTP path for the engine to reach a public or vendor-approved mirror, (2) a NO_PROXY list for private hosts, and (3) documentation for teammates so CI does not diverge from laptops. If you are comparing clients or upgrading cores, the Clash Meta upgrade guide stays the hub page for where Meta features land.
Recap: One Coherent Path for Docker and Clash
Align three layers: a host listener you can see with ss or netstat, an engine or Desktop configuration that points at a host-reachable HTTP URL using the right name instead of a blind loopback shortcut, and build-time HTTP proxy values that match, including NO_PROXY for anything that should stay on the LAN. Re-check after VPN connect and disconnect, after sleep and wake, and after any change to Clash’s mixed port, because the failure mode is almost always a silent drift. When the stack is sound, the same proxy you use for browsing also carries container layers predictably, without maintaining a second tunnel product beside Clash. If you are standardizing a client for your team, → Download Clash for free and experience the difference between a documented release channel and ad hoc binaries pulled from random threads.