百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

群晖无法拉取Docker镜像?最稳定的方法:搭建自己的加速服务!

off999 2025-03-28 20:16 63 浏览 0 评论

因为未知的原因,国内的各大 DockerHub 镜像服务器无法使用,导致在使用群晖时无法拉取镜像构建容器。

网上大部分的镜像加速服务都是通过 Cloudflare(CF) 搭建的,为什么都选它呢?因为 Cloudflare 提供了很多的免费服务,包括CDN加速、DNS解析、DDoS防护、访问规则、Workers等等。

老宁最开始也是通过CF为大家提供了免费镜像加速服务,不过为了账户安全,老宁在不久后便停止了服务(流量太大)。

这段时间很多粉丝问拉取镜像的问题,所以老宁今天就把 Workers 搭建的详细过程分享出来。通过在群晖上配置加速服务地址,就可以通过 Container Manager 或命令行方便地构建自己喜欢的容器了。

如果想拥有一个稳定的 Docker 加速服务,老宁强烈建议自己搭建!

Workers

Cloudflare Workers 是一种运行在 Cloudflare 全球网络边缘的轻量级、高性能的计算服务。开发者可以使用它来运行 JavaScript 代码,处理 HTTP 请求、修改响应或执行其他脚本任务,而无需管理服务器。

Cloudflare 的 Workers 每天为免费用户提供10万次请求。

前提

  • Cloudflare 账号
  • 域名(Worker 自带的域名无法访问,所以需要单独的域名)
  • 域名托管到了 Cloudflare

部署

打开 Cloudflare 仪表盘
https://dash.cloudflare.com/,在 Workers 和 Pages 选项卡中点击
创建 Worker按钮。

首先需要部署默认的worker才能对其进行修改。

再点击编辑代码,对worker代码进行修改。

接下来在worker中配置加速代码。打开 Github 项目
https://github.com/cmliu/CF-Workers-docker.io,把_worker.js文件中的代码复制粘贴到 Cloudflare 的编辑器中。(需覆盖原来的代码)

// _worker.js

// Docker镜像仓库主机地址
let hub_host = 'registry-1.docker.io';
// Docker认证服务器地址
const auth_url = 'https://auth.docker.io';
// 自定义的工作服务器地址
let workers_url = 'https://xxx/';

let 屏蔽爬虫UA = ['netcraft'];

// 根据主机名选择对应的上游地址
function routeByHosts(host) {
 // 定义路由表
 const routes = {
  // 生产环境
  "quay": "quay.io",
  "gcr": "gcr.io",
  "k8s-gcr": "k8s.gcr.io",
  "k8s": "registry.k8s.io",
  "ghcr": "ghcr.io",
  "cloudsmith": "docker.cloudsmith.io",
  "nvcr": "nvcr.io",
  
  // 测试环境
  "test": "registry-1.docker.io",
 };

 if (host in routes) return [ routes[host], false ];
 else return [ hub_host, true ];
}

/** @type {RequestInit} */
const PREFLIGHT_INIT = {
 // 预检请求配置
 headers: new Headers({
  'access-control-allow-origin': '*', // 允许所有来源
  'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS', // 允许的HTTP方法
  'access-control-max-age': '1728000', // 预检请求的缓存时间
 }),
}

/**
 * 构造响应
 * @param {any} body 响应体
 * @param {number} status 响应状态码
 * @param {Object} headers 响应头
 */
function makeRes(body, status = 200, headers = {}) {
 headers['access-control-allow-origin'] = '*' // 允许所有来源
 return new Response(body, { status, headers }) // 返回新构造的响应
}

/**
 * 构造新的URL对象
 * @param {string} urlStr URL字符串
 */
function newUrl(urlStr) {
 try {
  return new URL(urlStr) // 尝试构造新的URL对象
 } catch (err) {
  return null // 构造失败返回null
 }
}

function isUUID(uuid) {
 // 定义一个正则表达式来匹配 UUID 格式
 const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
 
 // 使用正则表达式测试 UUID 字符串
 return uuidRegex.test(uuid);
}

async function nginx() {
 const text = `
 
 
 
 Welcome to nginx!
 
 
 
 

Welcome to nginx!

If you see this page, the nginx web server is successfully installed and working. Further configuration is required.

For online documentation and support please refer to nginx.org.
Commercial support is available at nginx.com.

Thank you for using nginx.

` return text; } async function searchInterface() { const text = ` Docker Hub Search
<script> function performSearch() { const query = document.getElementById('search-input').value; if (query) { window.location.href = '/search?q=' + encodeURIComponent(query); } } document.getElementById('search-button').addEventListener('click', performSearch); document.getElementById('search-input').addEventListener('keypress', function(event) { if (event.key === 'Enter') { performSearch(); } }); </script> `; return text; } export default { async fetch(request, env, ctx) { const getReqHeader = (key) => request.headers.get(key); // 获取请求头 let url = new URL(request.url); // 解析请求URL const userAgentHeader = request.headers.get('User-Agent'); const userAgent = userAgentHeader ? userAgentHeader.toLowerCase() : "null"; if (env.UA) 屏蔽爬虫UA = 屏蔽爬虫UA.concat(await ADD(env.UA)); workers_url = `https://${url.hostname}`; const pathname = url.pathname; // 获取请求参数中的 ns const ns = url.searchParams.get('ns'); const hostname = url.searchParams.get('hubhost') || url.hostname; const hostTop = hostname.split('.')[0]; // 获取主机名的第一部分 let checkHost; // 在这里定义 checkHost 变量 // 如果存在 ns 参数,优先使用它来确定 hub_host if (ns) { if (ns === 'docker.io') { hub_host = 'registry-1.docker.io'; // 设置上游地址为 registry-1.docker.io } else { hub_host = ns; // 直接使用 ns 作为 hub_host } } else { checkHost = routeByHosts(hostTop); hub_host = checkHost[0]; // 获取上游地址 } const fakePage = checkHost ? checkHost[1] : false; // 确保 fakePage 不为 undefined console.log(`域名头部: ${hostTop}\n反代地址: ${hub_host}\n伪装首页: ${fakePage}`); const isUuid = isUUID(pathname.split('/')[1].split('/')[0]); if (屏蔽爬虫UA.some(fxxk => userAgent.includes(fxxk)) && 屏蔽爬虫UA.length > 0) { // 首页改成一个nginx伪装页 return new Response(await nginx(), { headers: { 'Content-Type': 'text/html; charset=UTF-8', }, }); } const conditions = [ isUuid, pathname.includes('/_'), pathname.includes('/r/'), pathname.includes('/v2/repositories'), pathname.includes('/v2/user'), pathname.includes('/v2/orgs'), pathname.includes('/v2/_catalog'), pathname.includes('/v2/categories'), pathname.includes('/v2/feature-flags'), pathname.includes('search'), pathname.includes('source'), pathname == '/', pathname == '/favicon.ico', pathname == '/auth/profile', ]; if (conditions.some(condition => condition) && (fakePage === true || hostTop == 'docker')) { if (env.URL302) { return Response.redirect(env.URL302, 302); } else if (env.URL) { if (env.URL.toLowerCase() == 'nginx') { //首页改成一个nginx伪装页 return new Response(await nginx(), { headers: { 'Content-Type': 'text/html; charset=UTF-8', }, }); } else return fetch(new Request(env.URL, request)); } else if (url.pathname == '/'){ return new Response(await searchInterface(), { headers: { 'Content-Type': 'text/html; charset=UTF-8', }, }); } const newUrl = new URL("https://registry.hub.docker.com" + pathname + url.search); // 复制原始请求的标头 const headers = new Headers(request.headers); // 确保 Host 头部被替换为 hub.docker.com headers.set('Host', 'registry.hub.docker.com'); const newRequest = new Request(newUrl, { method: request.method, headers: headers, body: request.method !== 'GET' && request.method !== 'HEAD' ? await request.blob() : null, redirect: 'follow' }); return fetch(newRequest); } // 修改包含 %2F 和 %3A 的请求 if (!/%2F/.test(url.search) && /%3A/.test(url.toString())) { let modifiedUrl = url.toString().replace(/%3A(?=.*?&)/, '%3Alibrary%2F'); url = new URL(modifiedUrl); console.log(`handle_url: ${url}`); } // 处理token请求 if (url.pathname.includes('/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, request), token_parameter); } // 修改 /v2/ 请求路径 if ( hub_host == 'registry-1.docker.io' && /^\/v2\/[^/]+\/[^/]+\/[^/]+$/.test(url.pathname) && !/^\/v2\/library/.test(url.pathname)) { //url.pathname = url.pathname.replace(/\/v2\//, '/v2/library/'); url.pathname = '/v2/library/' + url.pathname.split('/v2/')[1]; console.log(`modified_url: ${url.pathname}`); } // 更改请求的主机名 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 // 缓存时间 }; // 添加Authorization头 if (request.headers.has("Authorization")) { parameter.headers.Authorization = getReqHeader("Authorization"); } // 发起请求并处理响应 let original_response = await fetch(new Request(url, 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; // 修改 Www-Authenticate 头 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(request, new_response_headers.get("Location")); } // 返回修改后的响应 let response = new Response(original_text, { status, headers: new_response_headers }); return response; } }; /** * 处理HTTP请求 * @param {Request} req 请求对象 * @param {string} pathname 请求路径 */ function httpHandler(req, pathname) { const reqHdrRaw = req.headers; // 处理预检请求 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); } /** * 代理请求 * @param {URL} urlObj URL对象 * @param {RequestInit} reqInit 请求初始化对象 * @param {string} rawLen 原始长度 */ async function proxy(urlObj, reqInit, rawLen) { const res = await fetch(urlObj.href, reqInit); const resHdrOld = res.headers; const resHdrNew = new Headers(resHdrOld); // 验证长度 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 }); } async function ADD(envadd) { var addtext = envadd.replace(/[ |"'\r\n]+/g, ',').replace(/,+/g, ','); // 将空格、双引号、单引号和换行符替换为逗号 if (addtext.charAt(0) == ',') addtext = addtext.slice(1); if (addtext.charAt(addtext.length - 1) == ',') addtext = addtext.slice(0, addtext.length - 1); const add = addtext.split(','); return add; }

粘贴完毕后,把第8行url地址修改为自己的域名地址(域名为绑定到CF的域名,前缀任意)。

修改完毕后需要点击右上角的部署按钮进行部署。

部署成功后打开设置->域和路由->添加,新增一个路由。区域选择域名,路由输入前面在worker中配置的域名,域名后需加上/*。(可以先在这里配置好了再去修改脚本的域名)

回到 Cloudflare 主页,点击网站进入域名相关设置。

在DNS中新增一条A记录,名称为前面设置的域名前缀,可以设置为任意IP(2.2.2.2)。注意这里小云朵(代理)一定要打开。

稍等片刻,在浏览器中输入域名,出现以下界面就代表加速服务配置成功。

群晖配置

加速服务搭建完毕后再来看看如何在群晖上使用。

打开群晖 Container Manger 套件,编辑 Docker Hub(v1) 注册表。

勾选启用注册表镜像,粘贴CF设置的域名至输入框,再点击应用

现在可以直接在 Container Manager 的项目中通过compose 拉取镜像并构建容器。

在注册表中任然无法加载(应该可以通过修改脚本解决)。

当然也可以使用命令行拉取镜像。在群晖中建议使用第一种方法,一键设置加速地址不适用于群晖。

我是老宁

一个热爱技术的程序员和极客,群晖NAS深度玩家!

专注NAS相关技术分享,原创!干货!

觉得老宁的文章对你有帮助,记得点赞、收藏、加关注

相关推荐

滴滴注册司机车辆要求(滴滴注册司机车辆要求营运证)

以北海市为例,车辆要求如下:根据《北海市网约车管理实施细则》第十三条拟从事网约车经营的车辆,应当符合以下条件(一)七座及以下乘用车;(二)网约车经营车辆尾气排放必须达到国家规定的环保标准,发动机排量...

植物大战僵尸单机下载(单机植物大战僵尸免费版手机版)

您好,要在手机上下载并玩植物大战僵尸无尽版(Plantsvs.ZombiesEndlessVersion),可以按照以下步骤操作:1.打开手机的应用商店(如AppStore或Google...

赚钱app第一名(赚钱app第一名提现微信)
赚钱app第一名(赚钱app第一名提现微信)

比较大的公司产品就有:抖音极速版和今日头条极速版,欢迎扫码注册,注册后可以做任务赚金币,金币可以还钱,还可以提现;其他还有一些各种app,什么走路赚钱,读书赚钱等等的,不过不建议安装。头条、西瓜、抖音、快手等等都很靠谱啊,赚钱只能各凭本事了...

2026-02-03 09:51 off999

新东方英语线上课程报名(适合小学生免费学英语的软件)

新东方网上报名及支付操作流程一、登陆http://sz.xdf.cn/,进入新东方官网首页,搜索课程名称或者班号,选择适合您的课程二、进入搜课课程页面,点击【立即报名】三、去结算已选在购物车的课程四、...

应用宝一键安装(应用宝一键安装小米)
  • 应用宝一键安装(应用宝一键安装小米)
  • 应用宝一键安装(应用宝一键安装小米)
  • 应用宝一键安装(应用宝一键安装小米)
  • 应用宝一键安装(应用宝一键安装小米)
沫子漫画画免费读漫画在线观看

漫画结局就是夏沫和大少爷结婚,洛熙就在教堂听了什么后释然了,夏末还怀上了欧辰的宝宝,并且获得了最佳女主角的奖项。据文中洛熙的说法,是在第一次参加超级明星的那天晚上,在漫画第四话第28页,不过漫画上画的...

主题壁纸大全下载(好看的主题壁纸图片)

主题和壁纸有很大的区别,壁纸的话,你可以根据自己的喜好来设置你想要得到的壁纸,可以从你的相册里面挑选壁纸作为你的手机屏幕壁纸纸,但是主题的话,主题,他是属于一个系列的,当你换了手机主题之后,你的操作界...

网络电视下载什么软件看直播电视

1,饭团影视tv版  饭团影视是一款超好看的影视资源播放器软件轻松享受追剧的快感,无卡顿流畅播放,超强大的播放功能。  2,红影TV版  红影TV是一款电视点播软件,无需授权码,无需注册登录,软件完全...

如何快速学会cad制图(怎样能快速学会cad画图)

CAD可以自学啊,很简单,就是你报个班去学也就是那些简单点的东西,自学都可以搞定的,因为那些辅导班很少有教你实际的东西,大都是一些简单命令的操作。。。CAD也分好几种,机械,电气,建筑等等,你的先选好...

苹果手机微信安装(苹果手机微信安装教程)

苹果手机是目前市场上最受欢迎的智能手机之一,而微信也是最流行的即时通讯软件之一。在苹果手机上安装微信非常简单,只需按照以下步骤操作即可。步骤一:打开AppStore应用商店在苹果手机主屏幕上找到Ap...

手机在线测速(手机在线测速测网速)
  • 手机在线测速(手机在线测速测网速)
  • 手机在线测速(手机在线测速测网速)
  • 手机在线测速(手机在线测速测网速)
  • 手机在线测速(手机在线测速测网速)
firefox火狐浏览器官网(火狐浏览器官网最新版)

要在手机上登录火狐浏览器,首先需要在手机应用商店下载并安装火狐浏览器应用程序。安装完成后,打开应用程序并点击登录按钮。输入您的火狐账号和密码,然后点击登录。如果您还没有火狐账号,可以点击注册按钮创建一...

三星samsung官网(三星 官网)

三星中国官方网站www.samsung.com.cn点击网站顶栏的服务支持学校就可查询你所在地的售后服务经销商在哪里。当然你可以通过拨打三星的全国官方客服电话,查询离你最近的售后服务网点。三星的售后还...

制图软件手机版(cad免费制图软件手机版)

手机作图软件可以使用美图秀秀、光影魔术手。美图秀秀由美图网研发推出,是一款免费图片处理软件,不用学习就会用,比AdobePhotoshop简单很多。图片特效、美容、拼图、场景、边框、饰品等功能,加上...

做ppt用什么软件(ai做ppt用什么软件)

常用的PPT制作软件有officepowerpoint以及WPS。1.两款软件虽然都能制作PPT文件,但是在部分细节上并不兼容。2.PPT与WPS在动画效果上有各自不同的动画效果,文件转换时,缺少...

取消回复欢迎 发表评论: