docker pull镜像 代理 (持续维护)
方法0: 通过deno配置个ts 转发代理--推荐!!!
https://linux.do/t/topic/432248
const baseUrl = "your-cutom.domain"; // 这就是 deno 上建的项目名字。 起个好用、好记的名字
const routes = {
[baseUrl]: "https://registry-1.docker.io",
[`docker.${baseUrl}`]: "https://registry-1.docker.io",
[`quay.${baseUrl}`]: "https://quay.io",
[`gcr.${baseUrl}`]: "https://gcr.io",
[`k8s-gcr.${baseUrl}`]: "https://k8s.gcr.io",
[`k8s.${baseUrl}`]: "https://registry.k8s.io",
[`ghcr.${baseUrl}`]: "https://ghcr.io",
[`cloudsmith.${baseUrl}`]: "https://docker.cloudsmith.io",
};
function routeByHosts(host: string): string {
return routes[host] || "https://registry-1.docker.io";
}
/**
* 兼容官方镜像的匹配
* Docker official images like "golang" actually live under "/library/golang".
* So if the user’s request is /v2/golang/... we rewrite to /v2/library/golang/...
*/
function adjustDockerPath(pathname: string): string {
console.log(`[adjustDockerPath] Original path: ${pathname}`);
// 匹配 /v2/*/{manifests,blobs}/* 格式的路径
const regex = /^\/v2\/([^\/]+)\/(manifests|blobs)\/(.+)$/;
const match = pathname.match(regex);
if (!match) {
// console.log(`[adjustDockerPath] Path does not match pattern: ${pathname}`);
return pathname;
}
const imageName = match[1];
const type = match[2]; // manifests 或 blobs
const suffix = match[3]; // 标签或哈希值
// console.log(`[adjustDockerPath] Extracted image name: ${imageName}, type: ${type}, suffix: ${suffix}`);
// 判断是否为官方镜像
if (!imageName.includes('/')) {
const adjustedPath = `/v2/library/${imageName}/${type}/${suffix}`;
// console.log(`[adjustDockerPath] Adjusted path (official image): ${adjustedPath}`);
return adjustedPath;
}
// console.log(`[adjustDockerPath] Adjusted path (non-official image): ${pathname}`);
return pathname;
}
async function fetchToken(repository: string, authorization: string | null): Promise<Response> {
// Add "library/" prefix for official images
if (!repository.includes('/')) {
repository = `library/${repository}`;
}
// Always talk to Docker Hub auth server
const tokenUrl = new URL("https://auth.docker.io/token");
tokenUrl.searchParams.set("service", "registry.docker.io");
tokenUrl.searchParams.set("scope", `repository:${repository}:pull`);
// console.log(`Token request URL: ${tokenUrl}`);
// console.log(`Requesting token for repository: ${repository}`);
const headers = new Headers();
if (authorization && authorization.startsWith("Basic ")) {
// Only forward Basic credentials
headers.set("Authorization", authorization);
// console.log("Using Basic authorization for token request");
} else if (authorization && authorization.startsWith("Bearer ")) {
// We ignore existing Bearer tokens, requesting a fresh token from Docker Hub
// console.log("Bearer token received, requesting new token without forwarding it");
}
const tokenResp = await fetch(tokenUrl.toString(), { method: "GET", headers });
if (!tokenResp.ok) {
// console.error(`Token fetch failed: ${tokenResp.status}`);
const errorBody = await tokenResp.text();
// console.error(`Token error: ${errorBody}`);
} else {
const tokenData = await tokenResp.clone().json();
// console.log(`Token received successfully for ${repository}`);
}
return tokenResp;
}
/**
* For /v2/auth calls, parse `scope=...` to figure out the repository name,
* then fetch a token with "pull" scope from Docker Hub.
*/
async function handleV2AuthPath(request: Request): Promise<Response> {
console.log(`\n=== Handling auth request ===`);
const url = new URL(request.url);
const authorization = request.headers.get("Authorization");
let repository = "";
const scopeParam = url.searchParams.get("scope");
if (scopeParam) {
// e.g. "repository:library/golang:pull"
const match = scopeParam.match(/repository:([^:]+):/);
if (match) {
repository = match[1];
}
}
// fallback if somehow no repository was extracted
if (!repository) {
repository = "library/unknown";
}
return fetchToken(repository, authorization);
}
/**
* When Docker calls /v2/, it might get a 401 from upstream Docker Hub
* with a `WWW-Authenticate` header. We typically want to pass that along.
*/
async function handleV2Path(url: URL, authorization: string | null): Promise<Response> {
// console.log(`\n=== Handling /v2/ request ===`);
const upstream = routeByHosts(url.hostname);
const upstreamUrl = new URL(upstream + "/v2/");
const headers = new Headers();
if (authorization) {
headers.set("Authorization", authorization);
}
const resp = await fetch(upstreamUrl.toString(), { method: "GET", headers, redirect: "follow" });
// console.log(`V2 response status: ${resp.status}`);
// console.log("V2 response headers:", Object.fromEntries(resp.headers.entries()));
// If upstream returns 401, we should respond with a 401 to Docker
// that includes the same "WWW-Authenticate" scope.
if (resp.status === 401) {
const upstreamAuth = resp.headers.get("WWW-Authenticate");
if (upstreamAuth) {
// Just pass it along to Docker so Docker can request the correct scope
const outHeaders = new Headers(resp.headers);
// We could override realm= to point to our own /v2/auth, but typically
// you can just pass the entire upstreamAuth back. Or you can rewrite
// the realm to your own /v2/auth. But importantly, keep the scope the same.
outHeaders.set("WWW-Authenticate", rewriteAuthHeaderToSelf(url, upstreamAuth));
return new Response(await resp.text(), { status: 401, headers: outHeaders });
}
}
return resp;
}
/**
* The main proxy for regular registry requests, e.g. /v2/library/golang/manifests/latest
*/
async function handleRegistryRequest(request: Request, url: URL, upstream: string, authorization: string | null): Promise<Response> {
console.log(`\n=== Handling registry request ===`);
let adjustedPath = url.pathname;
if (upstream === "https://registry-1.docker.io") {
// console.log(`Original path: ${url.pathname}`);
adjustedPath = adjustDockerPath(url.pathname);
// console.log(`Adjusted path: ${adjustedPath}`);
}
const newUrl = new URL(upstream + adjustedPath + url.search);
// console.log(`Final URL: ${newUrl}`);
const headers = new Headers(request.headers);
headers.set("Host", new URL(upstream).host);
if (authorization) {
headers.set("Authorization", authorization);
}
const resp = await fetch(newUrl.toString(), {
method: request.method,
headers,
body: request.body,
redirect: "follow",
});
// console.log(`Response status: ${resp.status}`);
if (resp.status === 401) {
// **IMPORTANT**: If upstream returns 401 for pulling, pass
// the `WWW-Authenticate` header back to Docker, so Docker
// knows exactly which scope is needed.
const upstreamAuth = resp.headers.get("WWW-Authenticate");
if (upstreamAuth) {
// We rewrite the realm= so that Docker calls our /v2/auth instead of docker.io,
// but keep the same scope= value from upstream.
const outHeaders = new Headers(resp.headers);
outHeaders.set("WWW-Authenticate", rewriteAuthHeaderToSelf(url, upstreamAuth));
// Return 401 with the updated header.
return new Response(await resp.text(), { status: 401, headers: outHeaders });
}
}
if (!resp.ok) {
// console.error(`Error response status: ${resp.status}`);
// console.error("Error response headers:", Object.fromEntries(resp.headers.entries()));
const errorBody = await resp.clone().text();
// console.error(`Error body: ${errorBody}`);
}
return resp;
}
/**
* Utility: Docker expects the realm to point to your /v2/auth, but the scope from upstream remains intact.
* If upstream says:
* WWW-Authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:library/golang:pull"
* We want something like:
* Bearer realm="https://your-proxy-host/v2/auth",service="registry.docker.io",scope="repository:library/golang:pull"
*/
function rewriteAuthHeaderToSelf(url: URL, upstreamHeader: string): string {
// Typically the upstreamHeader is something like:
// "Bearer realm=\"https://auth.docker.io/token\",service=\"registry.docker.io\",scope=\"repository:library/golang:pull\",error=\"insufficient_scope\""
// We'll replace the realm= with your own /v2/auth endpoint
const yourAuthEndpoint = `https://${url.hostname}/v2/auth`;
return upstreamHeader.replace(
/realm="[^"]+"/,
`realm="${yourAuthEndpoint}"`
);
}
async function handleRequest(request: Request): Promise<Response> {
try {
const url = new URL(request.url);
const upstream = routeByHosts(url.hostname);
const authorization = request.headers.get("Authorization");
// console.log(`\n=== Request Info ===`);
// console.log(`Path: ${url.pathname}`);
// console.log(`Auth present: ${!!authorization}`);
if (url.pathname === "/v2/") {
return handleV2Path(url, authorization);
}
if (url.pathname === "/v2/auth") {
return handleV2AuthPath(request);
}
// All other /v2/<...> calls get proxied to upstream
return handleRegistryRequest(request, url, upstream, authorization);
} catch (err) {
console.error("Error handling request:", err);
return new Response("Internal Server Error", { status: 500 });
}
}
Deno.serve(handleRequest);
方法一: 经过github action 推送到阿里云自己仓库
https://github.com/qbmiller/docker_image_pusher
重命名下: docker tag docker.m.xxx.io/nginx/nginx:latest nginx/nginx:latest
方法二: cloudfare worker . 不过提示有封号风险...
https://github.com/cmliu/CF-Workers-docker.io
方法三: https://github.com/developer-fuck/socks2trojan
没试过,可以试试。
一个高效的协议转换工具,支持将 SOCKS5/HTTP 代理转换为 Trojan 协议。
方法四: https://github.com/dqzboy/Docker-Proxy
自己有阿里云 腾讯与 服务器的话,可以搭建一个 docker proxy 。
以下都已经失效-------------
[root@devtest-service mydata]# docker pull docker.m.daocloud.io/grafana/loki:3.0.0
[root@devtest-service ~]# docker tag docker.m.daocloud.io/grafana/loki:3.0.0 grafana/loki:3.0.0
[root@devtest-service ~]# docker push grafana/loki:3.0.0 //这是推送到仓库,不一定支持
拉下来名字是 docker.m.daocloud.io/grafana/loki 所以重命名下,方便用
举例:搭建个nginx
docker pull docker.m.daocloud.io/nginx/nginx:latest # 加了前缀
重命名下: docker tag docker.m.daocloud.io/nginx/nginx:latest nginx/nginx:latest
https://github.com/DaoCloud/public-image-mirror
老的不能删。只是别名alias