docker mirrors

鉴于目前国内无法下载docker image 的情况,收集了一些方案,并经过试用,整理如下:

镜像法

手工修改配置

特点:简单
国内无法下载docker image,多加几个源,总有能用的。

  • vim /etc/docker/daemon.json

    {
    "registry-mirrors": [
    "https://docker.m.daocloud.io",
    "https://hub.uuuadc.top",
    "https://dockerhub.jobcher.com",
    "https://dockerhub.icu",
    "https://dockerproxy.com",
    "https://docker.io",
    "https://huecker.io",
    "https://dockerhub.timeweb.cloud"
    ],
    "log-driver": "json-file",
    "log-opts": {
    "max-size": "10m",
    "max-file": "10"
    }
    }
  • restart

    systemctl daemon-reload && systemctl restart docker

用自动化脚本

特点:智能
使用下列脚本,自动挑选最快的镜像并生成配置。

  • 脚本如下:
#!/bin/bash
# shellcheck shell=bash
# shellcheck disable=SC2086

PATH=${PATH}:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin
export PATH

Blue="\033[1;34m"
Green="\033[1;32m"
Red="\033[1;31m"
Yellow="\033[1;33m"
NC="\033[0m"
INFO="[${Green}INFO${NC}]"
ERROR="[${Red}ERROR${NC}]"
WARN="[${Yellow}WARN${NC}]"

function INFO() {
    echo -e "${INFO} ${1}"
}
function ERROR() {
    echo -e "${ERROR} ${1}"
}
function WARN() {
    echo -e "${WARN} ${1}"
}

function docker_pull() {
    #[ -z "${config_dir}" ] && get_config_path
    local config_dir=${2:-"/etc/xiaoya"}
    mkdir -p "${config_dir}"
    local mirrors=("docker.io" "docker.fxxk.dedyn.io" "docker.m.daocloud.io" "docker.adysec.com" "registry-docker-hub-latest-9vqc.onrender.com" "docker.chenby.cn" "dockerproxy.com" "hub.uuuadc.top" "docker.jsdelivr.fyi" "docker.registry.cyou" "dockerhub.anzu.vip")
    if [ -s "${config_dir}/docker_mirrors.txt" ]; then
        mirrors=()
        while IFS= read -r line; do
            mirrors+=("$line")
        done < "${config_dir}/docker_mirrors.txt"
    else
        for mirror in "${mirrors[@]}"; do
            printf "%s\n" "$mirror" >> "${config_dir}/docker_mirrors.txt"
        done
    fi
    if command -v timeout > /dev/null 2>&1;then
        for mirror in "${mirrors[@]}"; do
            INFO "正在测试${mirror}代理点的连接性……"
            if timeout 30 docker pull "${mirror}/library/hello-world:latest"; then
                INFO "${mirror}代理点连通性测试正常!正在为您下载镜像……"
                for i in {1..2}; do
                    if timeout 300 docker pull "${mirror}/${1}"; then
                        INFO "${1} 镜像拉取成功!"
                        sed -i "/${mirror}/d" "${config_dir}/docker_mirrors.txt"
                        sed -i "1i ${mirror}" "${config_dir}/docker_mirrors.txt"
                        break;
                    else
                        WARN "${1} 镜像拉取失败,正在进行重试..."
                    fi
                done
                if [[ "${mirror}" == "docker.io" ]];then
                    docker rmi "library/hello-world:latest"
                    [ -n "$(docker images -q "${1}")" ] && return 0
                else
                    docker rmi "${mirror}/library/hello-world:latest"
                    [ -n "$(docker images -q "${mirror}/${1}")" ] && break
                fi
            fi
        done
    else
        timeout=20
        for mirror in "${mirrors[@]}"; do
            INFO "正在测试${mirror}代理点的连接性……"       
            docker pull "${mirror}/library/hello-world:latest" || true &
            pid=$!
            count=0
            while kill -0 $pid 2>/dev/null; do
                sleep 5
                count=$((count+5))
                if [ $count -ge $timeout ]; then
                    echo "Command timed out"
                    kill $pid
                    break
                fi
            done

            if [ $? -eq 0 ]; then
                INFO "${mirror}代理点连通性测试正常!正在为您下载镜像……"
                timeout=200
                for i in {1..2}; do
                    docker pull "${mirror}/${1}" || true &
                    pid=$!
                    count=0
                    while kill -0 $pid 2>/dev/null; do
                        sleep 5
                        count=$((count+5))
                        if [ $count -ge $timeout ]; then
                            echo "Command timed out"
                            kill $pid
                            break
                        fi
                    done
                done
                if [[ "${mirror}" == "docker.io" ]];then
                    docker rmi "library/hello-world:latest"
                    if [ -n "$(docker images -q "${1}")" ]; then
                        INFO "${1} 镜像拉取成功!"
                        sed -i "/${mirror}/d" "${config_dir}/docker_mirrors.txt"
                        sed -i "1i ${mirror}" "${config_dir}/docker_mirrors.txt"
                        return 0
                    else
                        WARN "${1} 镜像拉取失败,正在进行重试..."
                    fi
                else
                    docker rmi "${mirror}/library/hello-world:latest"
                    if [ -n "$(docker images -q "${mirror}/${1}")" ]; then
                        INFO "${1} 镜像拉取成功!"
                        sed -i "/${mirror}/d" "${config_dir}/docker_mirrors.txt"
                        sed -i "1i ${mirror}" "${config_dir}/docker_mirrors.txt"
                        break
                    else
                        WARN "${1} 镜像拉取失败,正在进行重试..."
                    fi
                fi
            fi
        done
    fi

    if [ -n "$(docker images -q "${mirror}/${1}")" ]; then
        docker tag "${mirror}/${1}" "${1}"
        docker rmi "${mirror}/${1}"
        return 0
    else
        ERROR "已尝试所有镜像代理拉取失败,程序退出,请检查网络后再试!"
        exit 1       
    fi
}

if [ -n "$1" ];then
    docker_pull $1 $2
else
    while :; do
        read -erp "请输入您要拉取镜像的完整名字(示例:ailg/alist:latest):" pull_img
        [ -n "${pull_img}" ] && break
    done
    docker_pull "${pull_img}"
fi

代理法

利用现成的代理

特点:能不能用随缘
找个现成的代理,关键在于能不能找到好用的、稳定的代理服务器,然后按照以下步骤修改配置。

mkdir /etc/systemd/system/docker.service.d
vim http-proxy.conf
# 填写如下内容
[Service]
Environment="HTTP_PROXY=http://代理服务器ip:port"
Environment="HTTPS_PROXY=http://代理服务器ip:port"  # 实际上,大多数https代理还是用http服务器

systemctl daemon-reload
systemctl restart docker

自建服务器搭建

特点:不差钱
找个海外的服务器,跑个Nginx,自己配置一个域名和证书,就可以搭建一个自己的镜像服务了。

  • docker-compose.yml

    services:
    nginx:
    image: nginx:latest
    container_name: nginx
    network_mode: host
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./certs:/certs:ro
      - ./html:/var/www/html
      - /var/log/nginx:/var/log/nginx
    restart: always
    
    # 用acme申请证书
    # https://github.com/acmesh-official/acme.sh/wiki/Run-acme.sh-in-docker#3-run-acmesh-as-a-docker-daemon
    acme:
    image: neilpang/acme.sh:3.0.7
    restart: always
    container_name: acme.sh
    command: ["daemon"]
    volumes:
      - ./html:/webroot
      - ./acme.sh:/acme.sh
      - ./certs:/ssl
  • ./nginx/conf.d/80.conf

    server {
    listen 80;
    server_tokens off;
    
    access_log /var/log/nginx/access_80.log;
    error_log /var/log/nginx/error_80.log;
    
    location  /.well-known/acme-challenge {
        default_type "text/plain";
        root /var/www/html;
    }
    }
  • ./nginx/conf.d/docker.conf

    server {
    listen 443 ssl;
    server_name hub.razeen.me;
    http2 on;
    
    ssl_certificate /certs/cert.pem;
    ssl_certificate_key /certs/key.pem;
    
    access_log /var/log/nginx/access_hub.log;
    error_log /var/log/nginx/error_hub.log;
    
    ssl_session_timeout 1d;
    ssl_session_cache shared:MozSSL:10m;  # about 40000 sessions
    ssl_session_tickets off;
    
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
    ssl_prefer_server_ciphers off;
    
    # OCSP stapling
    # ssl_stapling on;
    # ssl_stapling_verify on;
    
    # verify chain of trust of OCSP response using Root CA and Intermediate certs
    # ssl_trusted_certificate /certs/ca.pem;
    
    location / {
        proxy_pass https://registry-1.docker.io;  # Docker Hub 的官方镜像仓库
        proxy_set_header Host registry-1.docker.io;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    
        proxy_buffering off;
    
        proxy_set_header Authorization $http_authorization;
        proxy_pass_header  Authorization;
    
        # 对 upstream 状态码检查,实现 error_page 错误重定向
        proxy_intercept_errors on;
        # error_page 指令默认只检查了第一次后端返回的状态码,开启后可以跟随多次重定向。
        recursive_error_pages on;
        # 根据状态码执行对应操作,以下为301、302、307状态码都会触发
        error_page 301 302 307 = @handle_redirect;
    }
    
    location @handle_redirect {
        resolver 1.1.1.1;
        set $saved_redirect_location '$upstream_http_location';
        proxy_pass $saved_redirect_location;
    }
    }

借助大善人之手

特点:赛博大善人,名不虚传
上一个方法要一个海外的服务器,得花钱。要省钱可以用免费的 Cloudflare Workers 来代理。需要准备一个域名并托管到 Cloudflare。在 Workers 和 Pages > 创建 > 创建 Workers,然后添加以下代码:

  • worker.js
    import HTML from './docker.html';
    export default {
    async fetch(request) {
    const url = new URL(request.url);
    const path = url.pathname;
    const originalHost = request.headers.get("host");
    const registryHost = "registry-1.docker.io";
    if (path.startsWith("/v2/")) {
      const headers = new Headers(request.headers);
      headers.set("host", registryHost);
      const registryUrl = `https://${registryHost}${path}`;
      const registryRequest = new Request(registryUrl, {
        method: request.method,
        headers: headers,
        body: request.body,
        redirect: "follow",
      });
      const registryResponse = await fetch(registryRequest);
      console.log(registryResponse.status);
      const responseHeaders = new Headers(registryResponse.headers);
      responseHeaders.set("access-control-allow-origin", originalHost);
      responseHeaders.set("access-control-allow-headers", "Authorization");
      return new Response(registryResponse.body, {
        status: registryResponse.status,
        statusText: registryResponse.statusText,
        headers: responseHeaders,
      });
    } else {
      return new Response(HTML.replace(/{{host}}/g, originalHost), {
        status: 200,
        headers: {
          "content-type": "text/html"
        }
      });
    }
    }
    }
  • docker.html
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>镜像使用说明</title>
    <style>
        body {
            font-family: 'Roboto', sans-serif;
            margin: 0;
            padding: 0;
            background-color: #f4f4f4;
        }
        .header {
            background: linear-gradient(135deg, #667eea, #764ba2);
            color: #fff;
            padding: 20px 0;
            text-align: center;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }
        .container {
            max-width: 800px;
            margin: 40px auto;
            padding: 20px;
            background-color: #fff;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
            border-radius: 10px;
        }
        .content {
            margin-bottom: 20px;
        }
        .footer {
            text-align: center;
            padding: 20px 0;
            background-color: #333;
            color: #fff;
        }
        pre {
            background-color: #272822;
            color: #f8f8f2;
            padding: 15px;
            border-radius: 5px;
            overflow-x: auto;
        }
        code {
            font-family: 'Source Code Pro', monospace;
        }
        a {
            color: #4CAF50;
            text-decoration: none;
        }
        a:hover {
            text-decoration: underline;
        }
        @media (max-width: 600px) {
            .container {
                margin: 20px;
                padding: 15px;
            }
            .header {
                padding: 15px 0;
            }
        }
    </style>
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&family=Source+Code+Pro:wght@400;700&display=swap" rel="stylesheet">
    </head>
    <body>
    <div class="header">
        <h1>镜像使用说明</h1>
    </div>
    <div class="container">
        <div class="content">
            <p>为了加速镜像拉取,你可以使用以下命令设置 registry mirror:</p>
            <pre><code>sudo tee /etc/docker/daemon.json <<EOF
    {
    "registry-mirrors": ["https://{{host}}"]
    }
    EOF</code></pre>
            <p>为了避免 Worker 用量耗尽,你可以手动 pull 镜像然后 re-tag 之后 push 至本地镜像仓库:</p>
            <pre><code>docker pull {{host}}/library/alpine:latest # 拉取 library 镜像
    docker pull {{host}}/coredns/coredns:latest # 拉取 coredns 镜像</code></pre>
        </div>
    </div>
    <div class="footer">
        <p>Powered by Cloudflare Workers</p>
    </div>
    </body>
    </html>

留下评论

您的邮箱地址不会被公开。 必填项已用 * 标注