docker pull times out in China

In short: docker pull fails from inside China because Docker Hub's registry and its CDN are both unreliable there. A domestic mirror fixes the common images; anything else — private registries, ghcr.io, gcr.io, quay.io, or a docker build that fetches from GitHub — needs a proxy, and Docker does not pick up your shell's proxy variables by itself.

The errors you will see

Error response from daemon: Get "https://registry-1.docker.io/v2/":
net/http: TLS handshake timeout

error pulling image configuration: download failed after attempts=6:
dial tcp 3.94.224.37:443: i/o timeout

Both mean the same thing: the daemon reached the address and the connection was cut, not that the image is missing.

Step 1 — decide whether you need a mirror or a proxy

| What you pull | What fixes it | | --- | --- | | nginx, redis, python, node … official Docker Hub images | a registry mirror is enough and is faster | | ghcr.io/..., gcr.io/..., quay.io/..., registry.k8s.io/... | proxy — mirrors do not carry these | | your own private registry abroad | proxy | | docker build whose Dockerfile runs git clone, pip install, npm i | proxy, and it has to reach the build container |

Using a mirror where you actually need a proxy is the most common wasted hour.

Step 2a — registry mirror (Docker Hub images only)

Docker Desktop → Settings → Docker Engine, then add:

{
  "registry-mirrors": ["https://docker.m.daocloud.io"]
}

On a Linux server the same block goes in /etc/docker/daemon.json, then:

sudo systemctl restart docker

Verify it took effect:

docker info | grep -A2 "Registry Mirrors"

Step 2b — proxy for the daemon

The daemon is a separate process. Exporting HTTPS_PROXY in your shell does nothing for docker pull.

Docker Desktop (macOS / Windows) → Settings → Resources → Proxies → Manual proxy configuration:

HTTP  http://127.0.0.1:8668
HTTPS http://127.0.0.1:8668
Bypass  localhost,127.0.0.1,*.cn,*.aliyuncs.com

Linux with 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:8668"
Environment="HTTPS_PROXY=http://127.0.0.1:8668"
Environment="NO_PROXY=localhost,127.0.0.1,*.cn"
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

Step 3 — proxy inside docker build

Build containers get their own network namespace, so 127.0.0.1 there is the container, not your machine. Pass the host address explicitly:

docker build \
  --build-arg HTTP_PROXY=http://host.docker.internal:8668 \
  --build-arg HTTPS_PROXY=http://host.docker.internal:8668 \
  -t myimage .

On Linux host.docker.internal is not defined by default; use the bridge address (usually 172.17.0.1) or add --add-host=host.docker.internal:host-gateway.

With Stellar

Stellar listens on 127.0.0.1:8668 and already routes registry-1.docker.io, ghcr.io, gcr.io, quay.io, pkg-containers.githubusercontent.com and production.cloudflare.docker.com through its international lines, so once the daemon points at the proxy the pull just works — no mirror needed, including for private and non-Docker-Hub registries.

Related: terminal proxy setup · what needs a VPN in China · Linux install