banner
Lainbo

Lainbo's Blog

If you’re nothing without the suit, then you shouldn't have it.
github

自建Docker Hub镜像方法

自从 2023 年 5 月中旬,因为一股神秘力量导致 Docker 容器平台 https://hub.docker.com 无法访问了。

一年多过去了,截止目前(2024 年 6 月 9 日),南京大学、中科大、上海交大 目前明确停止 docker 镜像缓存服务。网易之前就死了,腾讯微软据说内网可用,阿里登陆后就可以拿到子域名,百度好像也挂了,dockerproxy 被墙。

所以,让我们来自建一个吧!(微笑脸)

本文介绍的两种方式并不是完全的零门槛,方式一需要你有自己的域名,方式二需要你有自己的境外服务器。

方式一:使用 Cloudflare Worker#

注册Cloudflare的方法不再赘述,一个邮箱就能注册,这个方式需要你有一个自己的域名

  1. 点击菜单栏的「Worker 和 Pages」,然后点击创建 Worker
    image

  2. 给你的 Worker 起个名字,比如 docker-proxy,点击「保存」,之后点击「完成」

    此时他应该会提示你 “恭喜!您的 Worker 已部署到以下区域:全球

  3. 点击右侧的「编辑代码」,将左侧已有代码删除,然后粘贴以下代码。需要将顶部的workers_url 修改为你要部署的域名

    • 代码内容
      'use strict'
      
      const hub_host = 'registry-1.docker.io'
      const auth_url = 'https://auth.docker.io'
      const workers_url = 'https://你的域名'
      //const home_page_url = '远程html链接'
      
      /** @type {RequestInit} */
      const PREFLIGHT_INIT = {
          status: 204,
          headers: new Headers({
              'access-control-allow-origin': '*',
              'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS',
              'access-control-max-age': '1728000',
          }),
      }
      
      /**
       * @param {any} body
       * @param {number} status
       * @param {Object<string, string>} headers
       */
      function makeRes(body, status = 200, headers = {}) {
          headers['access-control-allow-origin'] = '*'
          return new Response(body, {status, headers})
      }
      
      /**
       * @param {string} urlStr
       */
      function newUrl(urlStr) {
          try {
              return new URL(urlStr)
          } catch (err) {
              return null
          }
      }
      
      addEventListener('fetch', e => {
          const ret = fetchHandler(e)
              .catch(err => makeRes('cfworker error:\n' + err.stack, 502))
          e.respondWith(ret)
      })
      
      /**
       * @param {FetchEvent} e
       */
      async function fetchHandler(e) {
          const getReqHeader = (key) => e.request.headers.get(key);
      
          let url = new URL(e.request.url);
      
          if (url.pathname === '/') {
              // Fetch and return the home page HTML content
              //return fetch(home_page_url);
              return new Response(indexHtml, {
                  headers: {
                    'Content-Type': 'text/html'
                  }
                });
          }
      
          if (url.pathname === '/token') {
              let token_parameter = {
                  headers: {
                      'Host': 'auth.docker.io',
                      'User-Agent': getReqHeader("User-Agent"),
                      'Accept': getReqHeader("Accept"),
                      'Accept-Language': getReqHeader("Accept-Language"),
                      'Accept-Encoding': getReqHeader("Accept-Encoding"),
                      'Connection': 'keep-alive',
                      'Cache-Control': 'max-age=0'
                  }
              };
              let token_url = auth_url + url.pathname + url.search
              return fetch(new Request(token_url, e.request), token_parameter)
          }
      
          url.hostname = hub_host;
      
          let parameter = {
              headers: {
                  'Host': hub_host,
                  'User-Agent': getReqHeader("User-Agent"),
                  'Accept': getReqHeader("Accept"),
                  'Accept-Language': getReqHeader("Accept-Language"),
                  'Accept-Encoding': getReqHeader("Accept-Encoding"),
                  'Connection': 'keep-alive',
                  'Cache-Control': 'max-age=0'
              },
              cacheTtl: 3600
          };
      
          if (e.request.headers.has("Authorization")) {
              parameter.headers.Authorization = getReqHeader("Authorization");
          }
      
          let original_response = await fetch(new Request(url, e.request), parameter)
          let original_response_clone = original_response.clone();
          let original_text = original_response_clone.body;
          let response_headers = original_response.headers;
          let new_response_headers = new Headers(response_headers);
          let status = original_response.status;
      
          if (new_response_headers.get("Www-Authenticate")) {
              let auth = new_response_headers.get("Www-Authenticate");
              let re = new RegExp(auth_url, 'g');
              new_response_headers.set("Www-Authenticate", response_headers.get("Www-Authenticate").replace(re, workers_url));
          }
      
          if (new_response_headers.get("Location")) {
              return httpHandler(e.request, new_response_headers.get("Location"))
          }
      
          let response = new Response(original_text, {
              status,
              headers: new_response_headers
          })
          return response;
      
      }
      
      /**
       * @param {Request} req
       * @param {string} pathname
       */
      function httpHandler(req, pathname) {
          const reqHdrRaw = req.headers
      
          // preflight
          if (req.method === 'OPTIONS' &&
              reqHdrRaw.has('access-control-request-headers')
          ) {
              return new Response(null, PREFLIGHT_INIT)
          }
      
          let rawLen = ''
      
          const reqHdrNew = new Headers(reqHdrRaw)
      
          const refer = reqHdrNew.get('referer')
      
          let urlStr = pathname
      
          const urlObj = newUrl(urlStr)
      
          /** @type {RequestInit} */
          const reqInit = {
              method: req.method,
              headers: reqHdrNew,
              redirect: 'follow',
              body: req.body
          }
          return proxy(urlObj, reqInit, rawLen, 0)
      }
      
      /**
       *
       * @param {URL} urlObj
       * @param {RequestInit} reqInit
       */
      async function proxy(urlObj, reqInit, rawLen) {
          const res = await fetch(urlObj.href, reqInit)
          const resHdrOld = res.headers
          const resHdrNew = new Headers(resHdrOld)
      
          // verify
          if (rawLen) {
              const newLen = resHdrOld.get('content-length') || ''
              const badLen = (rawLen !== newLen)
      
              if (badLen) {
                  return makeRes(res.body, 400, {
                      '--error': `bad len: ${newLen}, except: ${rawLen}`,
                      'access-control-expose-headers': '--error',
                  })
              }
          }
          const status = res.status
          resHdrNew.set('access-control-expose-headers', '*')
          resHdrNew.set('access-control-allow-origin', '*')
          resHdrNew.set('Cache-Control', 'max-age=1500')
      
          resHdrNew.delete('content-security-policy')
          resHdrNew.delete('content-security-policy-report-only')
          resHdrNew.delete('clear-site-data')
      
          return new Response(res.body, {
              status,
              headers: resHdrNew
          })
      }
      
      const indexHtml = `
      <!DOCTYPE html>
      <html lang="zh-CN">
      
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>在Linux上设置Docker Hub Registry Mirror</title>
        <style>
          body {
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            line-height: 1.6;
            color: #333;
            background-color: #f7f7f7;
          }
      
          h1 {
            color: #0066cc;
            margin-bottom: 30px;
          }
      
          h2 {
            color: #0066cc;
            margin-top: 40px;
          }
      
          pre {
            background-color: #fff;
            padding: 15px;
            padding-top: 48px;
            overflow-x: auto;
            border-radius: 8px;
            box-shadow: 0 4px 14px rgba(0, 0, 0, 0.1);
          }
      
          ol {
            margin-top: 20px;
            padding-left: 20px;
          }
      
          li {
            margin-bottom: 10px;
          }
      
          code {
            font-family: Consolas, monospace;
            background-color: #fff;
            padding: 2px 4px;
            border-radius: 3px;
          }
      
          .container {
            background-color: #fff;
            padding: 30px;
            border-radius: 8px;
            box-shadow: 0 4px 14px rgba(0, 0, 0, 0.1);
          }
      
          .copy-btn {
            position: absolute;
            top: 10px;
            right: 10px;
            padding: 4px 10px;
            background-color: #0066cc;
            color: #fff;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
            transition: background-color 0.3s;
          }
      
          .copy-btn:hover {
            background-color: #0052a3af;
          }
      
          .code-wrapper {
            position: relative;
          }
        </style>
      </head>
      
      <body>
        <div class="container">
          <h1>在Linux上设置Docker Hub Registry Mirror</h1>
          <p>为了加速Docker镜像的下载速度,你可以设置Docker Hub的registry mirror</p>
      
          <h2>设置步骤</h2>
          <ol>
            <li>创建或编辑<code>/etc/docker/daemon.json</code>文件,添加以下内容:</li>
          </ol>
          <div class="code-wrapper">
            <pre>
      echo '{"registry-mirrors": ["${workers_url}"]}' | sudo tee /etc/docker/daemon.json > /dev/null</pre>
            <button class="copy-btn" onclick="copyCode(this)">复制</button>
          </div>
          <ol start="2">
            <li>重启Docker服务:</li>
          </ol>
          <div class="code-wrapper">
            <pre>sudo systemctl restart docker</pre>
            <button class="copy-btn" onclick="copyCode(this)">复制</button>
          </div>
      
          <p>设置完成后,Docker将会从您配置的registry mirror中拉取镜像,加速镜像下载过程。</p>
        </div>
      
        <script>
          function copyCode(btn) {
            const pre = btn.previousElementSibling;
            const code = pre.innerText;
            navigator.clipboard.writeText(code).then(function () {
              btn.innerText = '已复制';
              setTimeout(function () {
                btn.innerText = '复制';
              }, 2000);
            }, function () {
              alert('复制失败,请手动复制。');
            });
          }
        </script>
      </body>
      
      </html>
      `
      
  4. 点击右上角「部署」

  5. 因为 Worker 提供的域名是被 DNS 污染的,所以我们需要回到 Worker,如图依次点击,输入你刚刚在代码里填写的域名后提交更改,如果你的域名托管在 Cloudflare,则只需要等 2 分钟左右即可生效;如果在其他的服务商托管,你需要自己去 CNAME 一下
    image

  6. 此时我们在服务器中输入以下命令即可生效

    echo '{"registry-mirrors": ["https://你的域名"]}' | sudo tee /etc/docker/daemon.json > /dev/null
    
    sudo systemctl restart docker
    

方式二:使用境外服务器自建#

懒人脚本#

bash <(curl -sL https://raw.githubusercontent.com/lainbo/gists-hub/master/src/Linux/sh/deploy_registry.sh)

或者你想自己一步一步来#

  1. 创建一个 docker-compose.yml 文件,内容如下

    #version: '3' #最新版本docker 不需要此字段
    services:
      registry:
        image: registry:2
        ports:
          - "17951:5000"
        environment:
          REGISTRY_PROXY_REMOTEURL: https://registry-1.docker.io  # 上游源
          REGISTRY_STORAGE_CACHE_BLOBDESCRIPTOR: inmemory # 内存缓存
        volumes:
          - ./data:/var/lib/registry
    
  2. 运行起来,docker-compose up -d


以上不论是懒人脚本,还是自建创建 docker-compose 文件,都会利用 docker 在 17951 端口上起这个自建服务,接下来就是

  1. 反代一下上面写的 17951 端口,给个域名,https 证书加一下,DNS 解析添一条

  2. 此时我们在境内服务器中输入以下命令即可生效

    echo '{"registry-mirrors": ["https://你反代的域名"]}' | sudo tee /etc/docker/daemon.json > /dev/null
    
    sudo systemctl restart docker
    

方式三:使用huecker#

就是一个作者搭建的现成镜像

{
  "registry-mirrors": ["https://huecker.io"]
}

和一些其他值得尝试的境外镜像

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。