前言
- 最近国内的docker镜像站已经算是全军覆没了,具体什么原因也无从知晓,在拉取镜像的时候直接显示连接超时,为了能正常拉取镜像,最好的办法就是搭建一个自己专属的代理,这里我在GitHub上找到了一个非常不错的项目,可以完美解决镜像无法拉取的情况,而且还可以搭建出一个hub镜像站,方便搜索镜像名称。

什么是Cloudflare Wokers
- Cloudflare Workers 是 Cloudflare 提供的一个服务器less(无服务器)计算服务,它允许开发者在 Cloudflare 的全球边缘网络上运行 JavaScript、Rust 或其他 WASM(WebAssembly)支持的语言编写的代码。通过这种方式,你的代码能够在离用户最近的地理位置上运行,从而实现低延迟和高性能的用户体验。
以下是 Cloudflare Workers 的一些主要特点:
- 无服务器计算:
你不需要管理或维护服务器,而是只需要关注编写和部署代码。Cloudflare 会为你处理基础设施和扩展问题。 - 边缘计算:
代码直接运行在 Cloudflare 的全球边缘网络上,而不是集中在某个地区的服务器上。这意味着你的代码可以在离用户最近的地方运行,实现低延迟和高速度。 - 多语言支持:
虽然最初是为 JavaScript 设计的,但现在 Cloudflare Workers 也支持 Rust 和任何能编译成 WebAssembly 的语言。 - 简单的部署和管理:
Cloudflare 提供了简单的命令行工具和管理界面,使得部署和管理你的 Workers 变得非常简单。 - 内置的键值存储:
Cloudflare Workers 附带了一个名为 Workers KV 的内置键值存储解决方案,使得你可以在边缘网络上存储和检索数据。 - HTTP 路由和请求处理:
你可以轻松地创建 HTTP 路由,处理 HTTP 请求和响应,以及修改传入和传出的 HTTP 流量。 - 安全性和隐私:
Cloudflare Workers 运行在一个安全的沙箱环境中,以保护你的代码和数据。 - 集成和生态系统:
Cloudflare Workers 可以与 Cloudflare 的其他产品和服务集成,如 Cloudflare Pages、Durable Objects 和 Cloudflare Access 等。
Cloudflare Workers 为开发者提供了一个灵活、高性能、并且易于使用的边缘计算平台,使得你可以构建和部署全球分布式应用。
准备工作
- 一个 Cloudflare 账号,没有的话直接注册一个就行,这里也把注册链接贴出来:https://dash.cloudflare.com/sign-up
- Cloudflare 账号下需要添加一个域名,推荐到腾讯云注册一个域名,首年只需几块钱,特别便宜,这里也把优惠链接贴出:https://cloud.tencent.com/act/pro/domain_sales?from=19501 注册好域名之后,打开Cloudflare的控制台,点击添加站点,输入新注册的域名,之后按照提示,打开腾讯云的控制台,点击新注册的域名,打开修改DNS服务器,把原来的地址全部删掉,输入刚刚Cloudflare给的两个地址即可,稍等一会,再次打开Cloudflare的控制台,就能看到域名已经显示活动了,这就添加成功了

部署方式
- 打开Cloudflare的控制台,点Workers 和 Pages,在点击创建-创建Worker,这里可以自定义想要的名称,也可以使用默认的,我就输了个docker-proxy,点击创建,然后直接点编辑代码
- 打开项目地址:https://github.com/cmliu/CF-Workers-docker.io/blob/main/_worker.js
直接复制里面的代码,这里有些人可能打不开这个地址,这里也把代码直接放在下面了,点击小箭头可以打开查看,也可以直接点击复制
// _worker.js// Docker镜像仓库主机地址let hub_host = 'registry-1.docker.io'// Docker认证服务器地址const auth_url = 'https://auth.docker.io'// 自定义的工作服务器地址let workers_url = 'https://你的域名地址比如 docker.mydomain.com'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",// 测试环境"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<string, string>} 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 = `<!DOCTYPE html><html><head><title>Welcome to nginx!</title><style>body {width: 35em;margin: 0 auto;font-family: Tahoma, Verdana, Arial, sans-serif;}</style></head><body><h1>Welcome to nginx!</h1><p>If you see this page, the nginx web server is successfully installed andworking. Further configuration is required.</p><p>For online documentation and support please refer to<a href="http://nginx.org/">nginx.org</a>.<br/>Commercial support is available at<a href="http://nginx.com/">nginx.com</a>.</p><p><em>Thank you for using nginx.</em></p></body></html>`return text ;}export default {async fetch(request, env, ctx) {const getReqHeader = (key) => request.headers.get(key); // 获取请求头let url = new URL(request.url); // 解析请求URLconst 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;const hostname = url.searchParams.get('hubhost') || url.hostname;const hostTop = hostname.split('.')[0];// 获取主机名的第一部分const checkHost = routeByHosts(hostTop);hub_host = checkHost[0]; // 获取上游地址const fakePage = checkHost[1];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/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));}const newUrl = new URL("https://registry.hub.docker.com" + pathname + url.search);// 复制原始请求的标头const headers = new Headers(request.headers);// 确保 Host 头部被替换为 hub.docker.comheaders.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.searchreturn fetch(new Request(token_url, request), token_parameter)}// 修改 /v2/ 请求路径if (/^\/v2\/[^/]+\/[^/]+\/[^/]+$/.test(url.pathname) && !/^\/v2\/library/.test(url.pathname)) {url.pathname = url.pathname.replace(/\/v2\//, '/v2/library/');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 = pathnameconst 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.headersconst 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.statusresHdrNew.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, ','); // 将空格、双引号、单引号和换行符替换为逗号//console.log(addtext);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(',');//console.log(add);return add ;}
- 直接把原来的代码全选删除,把上面的代码复制进去,按照注释修改自定义服务器地址之后点击部署,待显示版本已保存的时候,点击左上角的箭头,在点击设置-触发器,把刚刚的自定义服务器地址添加到自定义域中,在添加一个路由地址,按照这个格式填即可:docker.mydomain.com/*
- 稍等一会,在浏览器打开刚刚的地址试一下,不出意外的话,dockerhub将会正常打开,这表示部署已经完成
使用说明
例如您的Workers项目域名为:docker.fxxk.dedyn.io;
1.官方镜像路径前面加域名
docker pull docker.fxxk.dedyn.io/stilleshan/frpc:latest
docker pull docker.fxxk.dedyn.io/library/nginx:stable-alpine3.19-perl
2.一键设置镜像加速
修改文件 /etc/docker/daemon.json(如果不存在则创建)
sudo mkdir -p /etc/dockersudo tee /etc/docker/daemon.json <<-'EOF'{"registry-mirrors": ["https://请替换为您自己的Worker自定义域名"]}EOFsudo systemctl daemon-reloadsudo systemctl restart docker
- 接下来实际测试一下,按照刚刚的方法设置一下镜像加速,再次拉取之前超时的镜像,这次就很顺利的拉下来了
