Using Cloudflare Worker to accelerate Docker images
The request was rejected because it was considered high risk
目录7 个章节
Preface
Developers have it harder now 🥱. Nowadays, Docker mirrors (Docker镜像) within China are also all gone 🈚️. Without mirrors, using Docker 🦈 is extremely difficult, and proxies 🌍 are very slow.
The only remaining option now is to build your own mirror, which is a pain 😭.
There are open-source projects on GitHub that can help you quickly set up your own mirror registry, but it’s still somewhat troublesome. Fortunately, Cloudflare is still alive for now 😮💨.
This article documents the method of using Cloudflare Worker to set up a Docker mirror (Docker镜像) 📒.
About Workers
Cloudflare Workers is a serverless computing platform provided by Cloudflare that allows you to run code written in languages like JavaScript, Rust, C, C++, and COBOL on Cloudflare's global edge network. It is primarily used to build high-performance, highly scalable web applications and APIs. 👍
Cloudflare Workers is ideal for handling various network requests, such as API gateways, content customization, and dynamic rendering, and can also be used to modify or determine how network requests are responded to. It empowers developers to rapidly deploy applications and services globally.
Some key features:
- Edge Computing: Since code is executed directly on Cloudflare's edge nodes, it can significantly reduce data transmission time and improve response speed.
- Serverless Architecture: You don't need to manage any servers or instances; just focus on writing and deploying code. Cloudflare takes care of running and scaling the code.
- Pay-as-you-go Billing: Cloudflare Workers' billing model is based on the number of requests and execution time, rather than pre-allocated resources, meaning you pay only for what you use.
- Highly Integrated: It can be tightly integrated with other Cloudflare services (like KV storage, Durable Objects, etc.), facilitating data storage and state management.
- Security: Since Workers run in an isolated environment, they provide a higher level of security compared to traditional servers or containers.
Create a worker
Open the Cloudflare Worker and Pages page, create a worker
Give it a descriptive name, such as docker-proxy or something similar
Then click the【Deploy】button
Edit Code
Once deployment is complete✅, click the [Edit Code] button next to it.
Input the following JavaScript code 👨🏫
You need to replace workers_url with your own domain, for example https://docker.example.com.
// Docker image registry host address
let hub_host = 'registry-1.docker.io'
// Docker authentication server URL
const auth_url = 'https://auth.docker.io'
// Custom worker server URL
let workers_url = 'https://your-domain'
let blockCrawlerUA = ['netcraft'];
// Select upstream address based on host name function routeByHosts(host) { // Define routing table const routes = { // Production environment "quay": "quay.io", "gcr": "gcr.io", "k8s-gcr": "k8s.gcr.io", "k8s": "registry.k8s.io", "ghcr": "ghcr.io", "cloudsmith": "docker.cloudsmith.io",
// Test environment
"test": "registry-1.docker.io",
};
if (host in routes) return [ routes[host], false ];
else return [ hub_host, true ];
}
/** @type {RequestInit} / const PREFLIGHT_INIT = { // Preflight request configuration headers: new Headers({ 'access-control-allow-origin': '', // Allow all origins 'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS', // Allowed HTTP methods 'access-control-max-age': '1728000', // Cache duration for preflight requests }), }
/**
- Construct response
- @param {any} body Response body
- @param {number} status Response status code
- @param {Object<string, string>} headers Response headers / function makeRes(body, status = 200, headers = {}) { headers['access-control-allow-origin'] = '' // Allow all origins return new Response(body, { status, headers }) // Return a newly constructed response }
/**
* Construct a new URL object
* @param {string} urlStr URL string
*/
function newUrl(urlStr) {
try {
return new URL(urlStr) // Attempt to create a new URL object
} catch (err) {
return null // Return null if creation fails
}
}
function isUUID(uuid) {
// Define a regular expression to match UUID format
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;
// Use a regular expression to test the UUID string 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 and
working. 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 ;
```markdown
export default {
async fetch(request, env, ctx) {
const getReqHeader = (key) => request.headers.get(key); // Get request header
let url = new URL(request.url); // Parse the request URL
const userAgentHeader = request.headers.get('User-Agent');
const userAgent = userAgentHeader ? userAgentHeader.toLowerCase() : "null";
if (env.UA) blockBotUA = blockBotUA.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]; // Get the first part of the hostname
const checkHost = routeByHosts(hostTop);
hub_host = checkHost[0]; // Get the upstream address
const fakePage = checkHost[1];
console.log(`Domain head: ${hostTop}\nReverse proxy address: ${hub_host}\nFake homepage: ${fakePage}`);
const isUuid = isUUID(pathname.split('/')[1].split('/')[0]);
if (blockCrawlerUA.some(fxxk => userAgent.includes(fxxk)) && blockCrawlerUA.length > 0){
//Redirect homepage to an nginx decoy page
return new Response(await nginx(), {
headers: {
'Content-Type': 'text/html; charset=UTF-8',
},
});
}
Translation notes:
屏蔽爬虫UA→blockCrawlerUA(a variable storing user-agent strings to block)首页改成一个nginx伪装页→ "Redirect homepage to an nginx decoy page"
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); // Get the complete registry URL
// Copy the original request headers const headers = new Headers(request.headers);
// Ensure the Host header is replaced with 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);
}
// Modify requests containing %2F and %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})
}
// Handle token request 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) }
// Modify /v2/ request path
if (/^/v2/[^/]+/[^/]+/[^/]+$/.test(url.pathname) && !/^/v2/library/.test(url.pathname)) {
url.pathname = url.pathname.replace(//v2//, '/v2/library/');
console.log(modified_url: ${url.pathname})
}
// Modify the request's hostname url.hostname = hub_host;
// Construct request parameters
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 // Cache duration
};
// 添加Authorization头
if (request.headers.has("Authorization")) {
parameter.headers.Authorization = getReqHeader("Authorization");
}
// Initiate request and process response
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));
}
// Handle redirect if (new_response_headers.get("Location")) { return httpHandler(request, new_response_headers.get("Location")) }
// Return the modified response
let response = new Response(original_text, {
status,
headers: new_response_headers
})
return response;
}
};
/**
- Handle HTTP Requests (Process)
- @param {Request} req The request object
- @param {string} pathname The request path */ function httpHandler(req, pathname) { const reqHdrRaw = req.headers
// Handle preflight request
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 = new URL(urlStr)
/** @type {RequestInit} */
const reqInit = {
method: req.method,
headers: reqHdrNew,
redirect: 'follow',
body: req.body
}
return proxy(urlObj, reqInit, rawLen)
/**
- Proxy request
- @param {URL} urlObj URL object
- @param {RequestInit} reqInit Request initialization object
- @param {string} rawLen Original length */ async function proxy(urlObj, reqInit, rawLen) { const res = await fetch(urlObj.href, reqInit) const resHdrOld = res.headers const resHdrNew = new Headers(resHdrOld)
// Verify length 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')
// Remove unnecessary headers 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, ','); // Replace spaces, double quotes, single quotes and newline characters with commas
//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 ;
}
OK👌, after entering the code, click [Deploy] again. Once the deployment is successful, you can open the docker🦈hub page.
Setting up the Domain
Didn't we just configure the domain in the code earlier❓
We need to continue with the configuration now.
Go back to the【Workers and Pages / docker-proxy】configuration page.
Click【Settings】-【Triggers】🥬
Under【Custom domains】, click to add, and enter the domain name, for example 🌰 docker.example.com
All done🤝. Cloudflare will automatically apply for an SSL certificate and handle everything else✔️
Configure Registry
Edit the /etc/docker/daemon.json file
{
"registry-mirrors": ["https://docker.example.com"],
"insecure-registries": ["docker.example.com"]
}
Restart Docker
sudo systemctl daemon-reload
sudo systemctl restart docker
Then, when you pull images, the speed will be fast like taking off 🛫✈️.
References
_EOF_
Using Cloudflare Worker to accelerate Docker images
- 本文作者:
- 程序设计实验室
- 版权声明:
- 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
相关文章
真正的生产力来了!Docker迁移部署两步搞定!
打包应用目录和命名数据卷,再复制过去解压启动,过程繁琐得令人发指。导出导入数据卷更是鸡肋,还不如直接重装。一键迁移脚本倒是挺酷,但谁会用这种花里胡哨的东西?
2025-06-30 · docker · 约 1 分钟
从挖矿木马入侵到 Docker Rootless 加固,我的服务器安全复盘
非要等服务器沦为矿场,才肯承认 Docker 默认 Root 权限是给黑客开后门?配置 Rootless 虽琐碎,但也比重装系统划算。连端口映射和 ACL 权限都搞不定,还是趁早关机退网,别在生产环境裸奔了。
2026-01-13 · docker · 约 1 分钟
国内使用docker镜像加速器
国内镜像源 国内的镜像源有 docker官方中国区 https://registry.docker-cn.com 网易 http://hub-mirror.c.163.com ustc http://docker.mirrors.ustc.edu.cn 阿里云 http://<你的ID>.mirror.aliyuncs.com 注意 注意registry-mirrors千万不要用https,而是用
2021-12-18 · docker · 约 1 分钟
一次生产环境的docker MySQL故障
问题 昨天下午本来要去吃下午茶,然后前端小伙伴突然说接口怎么崩了,我登上sentry一看,报错了 (2005, Unknown MySQL server host 'mysql' (-3)) 啥意思啊,连不上数据库,host是mysql因为这是在docker环境里 直接ssh连上服务器查看,没啥问题呀,MySQL容器也正常运行着 奇怪 然后我重启了一波docker,结果发现MySQL容器没有正常重
2022-05-27 · docker · 约 1 分钟
讨论
共 0 条加载评论中…