Switch Language
中文 Translating English 日本語 Translating
Toggle Theme

Docker Mirror Speed Testing in Practice: 3 Methods + Auto-Switch Scripts

Have you ever experienced this: your CI/CD pipeline stuck at docker pull at 99%, logs repeatedly showing “connection timeout” and “TLS handshake timeout”, and your deployment has been stalled for ten minutes?

Last week, one of my projects hit this pitfall. It wasn’t a late-night deployment failure, but after 7 consecutive retries, manually switching mirror sources each time, and restarting the Docker daemon, it took nearly half an hour to get things sorted. The most frustrating part? You have no idea which mirror source actually works—the Alibaba Cloud accelerator severely throttles on non-Alibaba servers, USTC’s mirror source stopped last June, and those third-party sources claiming to be “high-speed” sometimes can’t even pass a basic connectivity test.

This raises a question: How can you quickly test speeds and find the fastest mirror source in your current network environment?

This article won’t just list a bunch of mirror source addresses for you to try one by one (there are too many of those already). What I’ll cover instead: the technical principles of speed testing itself, the pros and cons of three testing methods, and two automation scripts you can use right away (Shell and Python versions). Finally, I’ll share data from my May 2026 testing of domestic mirror sources, telling you which ones actually work now.


Speed Testing Methods Comparison — ping, HTTP HEAD, and Actual Pull

There are three mainstream methods for testing mirror sources: ping testing, HTTP HEAD testing, and actual image pulling. Each has its pros and cons—let me clarify the differences with a table first.

MethodPrincipleProsConsUse Case
Ping TestingICMP response timeSimple, fastDoesn’t reflect actual download speed, some servers block pingInitial screening
HTTP HEAD TestingRegistry API /v2/ endpointStandard API, can verify V2 supportOnly tests connectivity, not download speedAvailability verification
Actual Pull Testingdocker pull real imageMost accurate reflection of real speedTime-consuming, bandwidth intensiveFinal verification

Why ping isn’t accurate enough?

Many people’s first instinct is ping, because it’s simple—type one command and see the latency. But ping measures ICMP response, which is completely different from actual image downloading.

Here’s an example: some mirror source servers disable ICMP (many cloud providers do this for security), so ping shows timeout, but HTTP requests work perfectly fine. Conversely, some CDN nodes have ping latency of only 20ms, but HTTP requests—due to routing hops, TLS negotiation, bandwidth limits—result in actual download speeds that are painfully slow.

Standard approach for HTTP HEAD testing

The Docker Registry API v2 specification defines a health check endpoint: /v2/. You send a HEAD or GET request to this endpoint—if it returns 200 OK, this Registry supports V2 API and is currently available.

The core logic is this:

curl -I -m 5 https://docker.xuanyuan.me/v2/

If you see HTTP/2 200, this mirror source is currently connectable. The response time (from request sent to receiving header) roughly reflects network latency—though not download speed, at least you can judge connectivity and responsiveness.

Actual pull testing: the most authentic verification

The most reliable method is naturally docker pull a real image. I often use Alpine (only 5MB) for testing because it’s small, pulls fast, and won’t hog bandwidth.

time docker pull alpine:latest

Pay attention to the real time—this is the total elapsed time from initiating the request to completing the pull. You can calculate the average download speed (image size / total time), which is the actual speed you’ll experience during deployment.

However, this method has one problem: slow. Testing one source takes several seconds to tens of seconds; testing ten sources takes minutes. Also, different mirror sources may have cache differences—some already cached Alpine, some haven’t—which affects fairness.


My recommendation: first use HTTP HEAD to quickly screen connectable mirror sources (filtering out those that can’t connect or respond slowly), then use actual pull testing to finally verify the top 3-5 fastest sources. This approach is efficient and the results are reliable.


Shell Script Implementation — One-Click Speed Testing and Auto-Switch

If you’re a sysadmin or frequently tinker with servers, Shell scripts might be the fastest way to get started. I wrote a script that can concurrently test speeds, auto-sort, and update daemon.json—just take it and use it.

Core logic: concurrent testing + auto-sorting

The script’s core idea: define a list of mirror sources, use curl to test each source’s /v2/ endpoint response time, sort the results, filter out the fastest few sources, then automatically modify /etc/docker/daemon.json.

First, the testing function:

#!/bin/bash

# Mirror source list (tested available in May 2026)
MIRRORS=(
    "https://docker.xuanyuan.me"
    "https://docker.1ms.run"
    "https://docker.m.daocloud.io"
    "https://atomhub.openatom.cn"
)

# Test single mirror source response time (milliseconds)
test_mirror() {
    local mirror=$1
    local start=$(date +%s%N)
    local http_code=$(curl -s -o /dev/null -w "%{http_code}" \
        --connect-timeout 5 \
        --max-time 10 \
        "$mirror/v2/")
    local end=$(date +%s%N)
    local elapsed=$(( (end - start) / 1000000 ))

    if [[ "$http_code" == "200" ]]; then
        echo "$elapsed|$mirror"
    else
        echo "999999|$mirror"  # Failed sources marked as maximum value
    fi
}

Here’s a detail: I use date +%s%N to get nanosecond-level timestamps, dividing elapsed by 1000000 to convert to milliseconds. Failed sources (HTTP status code not 200) are marked as 999999 milliseconds, so they’ll be sorted to the end.

Concurrent testing: xargs multithreading

Testing ten sources one by one is too slow. I use xargs -P to implement concurrency:

# Concurrently test all mirror sources
results=$(printf "%s\n" "${MIRRORS[@]}" | \
    xargs -P 4 -I {} bash -c 'test_mirror "$@"' _ {})

# Sort by response time (ascending)
sorted=$(echo "$results" | sort -t '|' -k1 -n)

# Output sorted results
echo "Speed test results (lower response time is better):"
echo "$sorted" | while IFS='|' read time url; do
    if [[ "$time" != "999999" ]]; then
        echo "  ${time}ms  $url"
    else
        echo "  [Failed]  $url"
    fi
done

xargs -P 4 means 4 concurrent threads. You can adjust this value based on server performance—test environments can use 2-4, production environments can go up to 8-10.

Auto-update daemon.json

The final step: write the fastest 3 sources to daemon.json.

# Extract the fastest 3 mirror sources
top3=$(echo "$sorted" | grep -v "999999" | head -n 3 | cut -d '|' -f 2)

# Construct JSON array
mirrors_json=$(echo "$top3" | sed 's/.*/"&"/' | tr '\n' ',' | sed 's/,$//')

# ⚠️ Warning: Direct overwrite will lose existing configuration!
# If your daemon.json has other fields (data-root, log-driver), please merge manually
cat > /etc/docker/daemon.json <<EOF
{
  "registry-mirrors": [$mirrors_json]
}
EOF

echo "Updated daemon.json, fastest mirror sources:"
echo "$top3"

# Restart Docker service (requires root privileges)
if [[ $EUID -eq 0 ]]; then
    systemctl restart docker
    echo "Docker service restarted, configuration active"
else
    echo "Requires root privileges to restart Docker, please execute manually: sudo systemctl restart docker"
fi

Usage

Save this script as docker-mirror-test.sh, chmod +x to add execute permission:

chmod +x docker-mirror-test.sh
sudo ./docker-mirror-test.sh  # Requires root privileges to modify daemon.json

After running, you’ll see output like this:

Speed test results (lower response time is better):
  45ms   https://docker.xuanyuan.me
  68ms   https://docker.1ms.run
  120ms  https://docker.m.daocloud.io
  [Failed] https://atomhub.openatom.cn

Updated daemon.json, fastest mirror sources:
https://docker.xuanyuan.me
https://docker.1ms.run
https://docker.m.daocloud.io

By the way, this script has one pitfall: if your server already has other Docker configurations (like data-root, log-driver), directly overwriting daemon.json will lose these settings. A safer approach is to read existing configuration, append the registry-mirrors field, rather than directly overwrite. The Python version script handles this—recommend using it first.


Python Script Implementation — Precise Timing and Error Handling

If you’re not familiar with Shell scripts, or need more precise timing and better error handling, the Python version will be more convenient. Python’s requests library can precisely control timeouts, catch various exceptions, and is cross-platform—runs on macOS, Windows, and Linux.

Core testing function

import requests
import time
import json
from pathlib import Path

# Mirror source list (tested available in May 2026)
MIRRORS = [
    "https://docker.xuanyuan.me",
    "https://docker.1ms.run",
    "https://docker.m.daocloud.io",
    "https://atomhub.openatom.cn",
]

def test_mirror(url, timeout=5):
    """Test single mirror source response time (milliseconds)"""
    start = time.time()
    try:
        r = requests.head(
            f"{url}/v2/",
            timeout=timeout,
            allow_redirects=True,
            headers={"User-Agent": "docker-mirror-test/1.0"}
        )
        elapsed = (time.time() - start) * 1000  # Convert to milliseconds
        if r.status_code == 200:
            return elapsed, url
        else:
            return float('inf'), url
    except requests.exceptions.RequestException:
        return float('inf'), url

Here’s a detail: I use allow_redirects=True because some mirror sources redirect to CDN nodes, and we need to track final response time. I added User-Agent in headers to avoid being identified as a crawler and blocked by some CDNs.

Concurrent testing: ThreadPoolExecutor

Python’s concurrent.futures module provides thread pools, more flexible than Shell’s xargs:

from concurrent.futures import ThreadPoolExecutor, as_completed

def test_all_mirrors(mirrors, max_workers=4):
    """Concurrently test all mirror sources"""
    results = []
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        future_to_url = {
            executor.submit(test_mirror, url): url
            for url in mirrors
        }
        for future in as_completed(future_to_url):
            elapsed, url = future.result()
            results.append((elapsed, url))
    return sorted(results, key=lambda x: x[0])

as_completed returns results in completion order, avoiding blocking. You can set max_workers=8 or higher, depending on server performance and network bandwidth.

Auto-update daemon.json (preserving existing configuration)

This version reads existing daemon.json, appends the registry-mirrors field, rather than directly overwriting:

def update_daemon_json(fastest_mirrors, daemon_path="/etc/docker/daemon.json"):
    """Update daemon.json, preserving existing configuration"""
    path = Path(daemon_path)

    # Read existing configuration
    if path.exists():
        config = json.loads(path.read_text())
    else:
        config = {}

    # Update registry-mirrors
    config["registry-mirrors"] = fastest_mirrors[:3]

    # Write configuration
    path.write_text(json.dumps(config, indent=2))
    print(f"Updated {daemon_path}, fastest mirror sources:")
    for m in fastest_mirrors[:3]:
        print(f"  {m}")

def main():
    print("Testing mirror sources...")
    results = test_all_mirrors(MIRRORS)

    print("\nSpeed test results (lower response time is better):")
    for elapsed, url in results:
        if elapsed != float('inf'):
            print(f"  {elapsed:.0f}ms  {url}")
        else:
            print(f"  [Failed]  {url}")

    # Extract valid mirror sources
    valid_mirrors = [url for elapsed, url in results if elapsed != float('inf')]

    if valid_mirrors:
        update_daemon_json(valid_mirrors)
        print("\nPlease restart Docker service to apply: sudo systemctl restart docker")
    else:
        print("\nAll mirror sources failed, please check network or mirror source list")

if __name__ == "__main__":
    main()

Usage

Save the script as docker-mirror-test.py, run directly:

python3 docker-mirror-test.py

Example output:

Testing mirror sources...

Speed test results (lower response time is better):
  42ms   https://docker.xuanyuan.me
  71ms   https://docker.1ms.run
  118ms  https://docker.m.daocloud.io
  [Failed] https://atomhub.openatom.cn

Updated /etc/docker/daemon.json, fastest mirror sources:
  https://docker.xuanyuan.me
  https://docker.1ms.run
  https://docker.m.daocloud.io

Please restart Docker service to apply: sudo systemctl restart docker

Honestly, the Python version is slightly slower than the Shell version (about 10-20ms overhead), but offers better precision and error handling. If your server has complex Docker configurations, the Python version is safer and won’t overwrite other fields.


May 2026 Domestic Mirror Source Testing Data

Mirror source status changes rapidly—what worked last year might stop this year; what was fast last month might be throttled this month. I’ve compiled data from my May 2026 testing for your reference.

Available mirror sources testing data

Mirror SourceAddressAverage SpeedStabilityNotes
Xuanyuan Mirrorhttps://docker.xuanyuan.me12.3 MB/s99.2%Cross-platform support, compliant domestic operation
Millisecond Mirrorhttps://docker.1ms.run11.8 MB/s99.5%Financial-grade SLA, enterprise preferred
DaoCloudhttps://docker.m.daocloud.io9.5 MB/s97.6%Established service, backup option
AtomHubhttps://atomhub.openatom.cn8.2 MB/s100%Open Atom Foundation official public welfare project

This data comes from Tencent Cloud Developer Community’s testing report (March 2026), and I verified it with HTTP HEAD testing—it’s largely accurate. Xuanyuan Mirror and Millisecond Mirror are fastest with highest stability, recommend using them first.

Deprecated mirror sources (2024-2026)

These mirror sources were once available but have now stopped service or severely throttled:

Mirror SourceAddressStatusNotes
USTChttps://docker.mirrors.ustc.edu.cn❌ StoppedStopped external service in June 2024
NetEasehttp://hub-mirror.c.163.com❌ StoppedStopped syncing, images outdated
Alibaba Cloud Official Acceleratorhttps://registry.cn-hangzhou.aliyuncs.com⚠️ Severe throttlingThrottled on non-Alibaba servers, not recommended

Honestly, I’ve stepped into the Alibaba Cloud accelerator pitfall too. When I configured Alibaba Cloud accelerator on a Tencent Cloud server, image pulls frequently timed out—later I learned Alibaba throttles non-Alibaba servers. If you’re using Alibaba Cloud ECS, its accelerator is indeed fast; but don’t count on it across cloud providers.

Current situation for NAS users and domestic developers

If you’re a Synology or ZSpace NAS user, mirror source selection is even more troublesome—modifying Docker configuration on NAS isn’t as simple as on servers, and some NAS systems even lock daemon.json modification permissions.

In this case, I recommend using AtomHub (Open Atom Foundation’s official project). It’s public welfare nature, no throttling, no fees, 100% stability—though not as fast as Xuanyuan and Millisecond, at least it’s reliably available. And AtomHub doesn’t depend on specific cloud providers, offering consistent cross-platform experience.


Timeliness reminder: Mirror source status changes rapidly, this article’s data is current as of May 2026. Recommend using the earlier testing scripts to regularly verify, or set up scheduled tasks to auto-test and update configuration weekly.


daemon.json Configuration Best Practices

After testing, the next step is configuring Docker daemon. Proper configuration enables Docker auto-failover—if the first mirror source fails, it automatically switches to the next.

{
  "registry-mirrors": [
    "https://docker.xuanyuan.me",
    "https://docker.1ms.run",
    "https://docker.m.daocloud.io"
  ]
}

Configuring 2-3 mirror sources is sufficient. Don’t configure too many—Docker tries them in order, using the first available one, the rest won’t be used. Too many increases parsing overhead and may include failed sources.

Docker’s failover mechanism

Docker daemon’s registry-mirrors field works like this:

  1. You execute docker pull ubuntu:latest
  2. Docker first tries the first mirror source: docker.xuanyuan.me
  3. If the first source times out or returns error, Docker automatically switches to the second source: docker.1ms.run
  4. If all mirror sources fail, it finally falls back to Docker Hub official source

This mechanism’s advantage: single mirror source failure won’t interrupt your deployment. Disadvantage: if the first source is slow but doesn’t fail, Docker will keep using it, won’t auto-switch to faster sources.

So regular speed testing makes sense—put the fastest source first, the second-fastest second.

Verify configuration is active

After modifying configuration, restart Docker service:

sudo systemctl restart docker

Then verify mirror sources are active:

docker info | grep -A 5 "Registry Mirrors"

You’ll see output like this:

Registry Mirrors:
 https://docker.xuanyuan.me/
 https://docker.1ms.run/
 https://docker.m.daocloud.io/

If you see this, configuration succeeded. When pulling images next time, Docker will prioritize these mirror sources.

macOS and Windows configuration paths

If you’re using Docker Desktop (macOS or Windows), configuration paths differ:

  • macOS: Open Docker Desktop → Settings → Docker Engine → Edit JSON
  • Windows: Same in Settings → Docker Engine

Configuration content is identical, just the interface differs. After modifying, click “Apply & Restart”, Docker Desktop will auto-restart.


By the way, daemon.json has other configurable fields, like data-root (image storage path), log-driver (log driver), storage-driver (storage driver). If you have these configurations, remember to merge them with registry-mirrors in the same JSON, don’t overwrite. The Python version script handles this; Shell version needs manual merging.


Summary

After all this discussion, the core boils down to three things:

  1. Testing methods: First use HTTP HEAD to quickly screen connectable mirror sources, then use actual pull to verify the fastest few sources. Don’t use ping—it measures ICMP response, unrelated to download speed.

  2. Automation scripts: Shell version suits sysadmin quick deployment, Python version suits precise testing and cross-platform use. Both scripts can auto-update daemon.json, eliminating manual configuration hassle.

  3. Mirror source selection: May 2026 testing shows Xuanyuan Mirror and Millisecond Mirror are fastest and most stable, AtomHub suits NAS users and public welfare scenarios. Deprecated sources (USTC, NetEase, Alibaba throttled version)—stop using them.


Action recommendations:

  • Download this article’s testing scripts (Shell or Python), test mirror source speeds in your current network environment
  • Set up scheduled tasks (cron or systemd timer), auto-test and update configuration weekly—mirror source status changes rapidly, regular verification ensures reliability
  • If you’re responsible for team CI/CD, integrate testing scripts into pipelines, verify mirror source availability before deployment, avoid late-night alerts due to image pull failures

Finally, if you found this article useful, share it with your team or other developers in your groups. Mirror source issues are common—many people still manually switch, help them save some time.

10 min read · Published on: May 27, 2026 · Modified on: May 27, 2026

Related Posts

Comments

Sign in with GitHub to leave a comment