一世贪欢的私域

一世贪欢的私域

自建Docker镜像

3
2025-11-21
自建Docker镜像

自建Docker镜像

众所周知,2024年6月7日GFW正式DNS污染+SNI阻断了docker.com及其相关域名。导致境内无法拉取Docker镜像。

一、切换镜像

mkdir /etc/docker
vim /etc/docker/daemon.json
{
  "registry-mirrors": ["https://hub.bravexist.cn"]
}
sudo systemctl daemon-reload
sudo systemctl restart docker

二、搭建自己的镜像源

思路来源:nodeseek论坛 ,让Claude 改了改。修复了直接拉取官方镜像 library 会失败的问题。

总之,想要舒服的使用,还是得自己搭建。

2.1 必备条件

  • 国外服务器,最好是无限流量的机器
  • 域名 hub.bravexist.cn

安装nginx,配置反向代理,主要是这三个location,需要将hub.bravexist.cn 换成自己的域名。

# Docker Registry 代理
    location /v2/ {
        proxy_pass https://registry-1.docker.io;
        proxy_set_header Host registry-1.docker.io;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # 超时设置
        proxy_connect_timeout 900;
        proxy_send_timeout 900;
        proxy_read_timeout 900;
        
        # SSL 配置
        proxy_ssl_server_name on;
        proxy_ssl_protocols TLSv1.2 TLSv1.3;
        
        # 关闭缓存
        proxy_buffering off;
        proxy_request_buffering off;
        
        # 认证相关
        proxy_set_header Authorization $http_authorization;
        proxy_pass_header Authorization;
        
        # 重写 www-authenticate 头
        proxy_hide_header www-authenticate;
        add_header www-authenticate 'Bearer realm="https://hub.bravexist.cn/token",service="registry.docker.io"' always;
        
        # ⭐ 处理重定向 - 只捕获 30x 状态码
        proxy_intercept_errors on;
        recursive_error_pages on;
        error_page 301 302 307 = @handle_redirect;
    }
    
    # Token 认证
    location /token {
        resolver 1.1.1.1 8.8.8.8 valid=300s;
        resolver_timeout 5s;
        
        proxy_pass https://auth.docker.io;
        proxy_set_header Host auth.docker.io;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        proxy_set_header Authorization $http_authorization;
        proxy_pass_header Authorization;
        
        proxy_buffering off;
        
        proxy_ssl_server_name on;
        proxy_ssl_protocols TLSv1.2 TLSv1.3;
    }
    
    # ⭐ 重定向处理 - 简化版本
    location @handle_redirect {
        internal;
        resolver 1.1.1.1 8.8.8.8 valid=300s;
        resolver_timeout 10s;
        
        set $redirect_uri '$upstream_http_location';
        proxy_pass $redirect_uri;
        
        # SSL 设置
        proxy_ssl_server_name on;
        proxy_ssl_protocols TLSv1.2 TLSv1.3;
        proxy_ssl_session_reuse off;
        
        # HTTP 版本和连接
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        
        # 超时(大文件下载)
        proxy_connect_timeout 900;
        proxy_send_timeout 900;
        proxy_read_timeout 900;
        send_timeout 900;
        
        # 禁用缓冲
        proxy_buffering off;
        proxy_request_buffering off;
        
        # ⭐ 不传递可能干扰 CDN 的头部
        proxy_pass_request_headers off;
        proxy_set_header Host $proxy_host;
        proxy_set_header User-Agent $http_user_agent;
        proxy_set_header Accept-Encoding "";
    }

配合Gemini写的静态页面。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Docker Registry Mirror - BraveXist</title>
    <style>
        :root {
            --bg-color: #0f172a;
            --card-bg: rgba(30, 41, 59, 0.7);
            --accent-color: #38bdf8;
            --accent-glow: #0ea5e9;
            --text-color: #e2e8f0;
            --code-bg: #1e1e1e;
            --success-color: #4ade80;
        }

        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        body {
            font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
            background: linear-gradient(-45deg, #0f172a, #1e1b4b, #312e81, #0f172a);
            background-size: 400% 400%;
            animation: gradientBG 15s ease infinite;
            color: var(--text-color);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
        }

        @keyframes gradientBG {
            0% { background-position: 0% 50%; }
            50% { background-position: 100% 50%; }
            100% { background-position: 0% 50%; }
        }

        .container {
            width: 100%;
            max-width: 800px;
            background: var(--card-bg);
            backdrop-filter: blur(16px);
            -webkit-backdrop-filter: blur(16px);
            border: 1px solid rgba(255, 255, 255, 0.1);
            border-radius: 24px;
            padding: 40px;
            box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
        }

        header {
            text-align: center;
            margin-bottom: 40px;
        }

        h1 {
            font-size: 2.5rem;
            font-weight: 800;
            background: linear-gradient(to right, #38bdf8, #818cf8);
            -webkit-background-clip: text;
            background-clip: text;
            color: transparent;
            margin-bottom: 10px;
            text-shadow: 0 0 30px rgba(56, 189, 248, 0.3);
        }

        .subtitle {
            color: #94a3b8;
            font-size: 1.1rem;
        }

        .section-title {
            font-size: 1.2rem;
            margin-bottom: 15px;
            color: var(--accent-color);
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .section-title::before {
            content: '';
            display: block;
            width: 4px;
            height: 20px;
            background: var(--accent-color);
            border-radius: 2px;
            box-shadow: 0 0 10px var(--accent-color);
        }

        .code-wrapper {
            position: relative;
            margin-bottom: 30px;
            background: var(--code-bg);
            border-radius: 12px;
            border: 1px solid rgba(255, 255, 255, 0.05);
            overflow: hidden;
            transition: transform 0.2s;
        }

        .code-wrapper:hover {
            border-color: rgba(56, 189, 248, 0.3);
        }

        pre {
            padding: 20px;
            padding-right: 60px;
            overflow-x: auto;
            color: #d4d4d4;
            font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
            font-size: 0.95rem;
            line-height: 1.6;
        }

        .copy-btn {
            position: absolute;
            top: 10px;
            right: 10px;
            background: rgba(255, 255, 255, 0.1);
            border: none;
            color: var(--text-color);
            padding: 8px 12px;
            border-radius: 6px;
            cursor: pointer;
            font-size: 0.8rem;
            transition: all 0.3s ease;
            display: flex;
            align-items: center;
            gap: 5px;
        }

        .copy-btn:hover {
            background: var(--accent-color);
            color: #000;
        }

        .copy-btn.copied {
            background: var(--success-color);
            color: #000;
        }

        footer {
            margin-top: 50px;
            text-align: center;
            border-top: 1px solid rgba(255, 255, 255, 0.05);
            padding-top: 30px;
        }

        .badges {
            display: flex;
            justify-content: center;
            gap: 15px;
            margin-bottom: 20px;
            flex-wrap: wrap;
        }

        .badges img {
            height: 25px;
            border-radius: 4px;
            transition: transform 0.3s;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
        }

        .badges img:hover {
            transform: translateY(-3px);
        }

        .footer-text {
            color: #64748b;
            font-size: 0.9rem;
        }

        .footer-link {
            color: var(--accent-color);
            text-decoration: none;
            font-weight: 600;
            transition: color 0.3s;
            position: relative;
        }
        
        .footer-link:hover {
            color: #818cf8;
            text-shadow: 0 0 10px rgba(129, 140, 248, 0.5);
        }

        /* Syntax Highlighting Simulation */
        .cmd { color: #ffd700; }
        .path { color: #9cdcfe; }
        .str { color: #ce9178; }
        .key { color: #9cdcfe; }

    </style>
</head>
<body>

    <div class="container">
        <header>
            <h1>Docker Mirror</h1>
            <p class="subtitle">高速 • 稳定 • 私有化加速服务</p>
        </header>

        <div class="section-title">1. 创建/编辑配置文件</div>
        <div class="code-wrapper">
            <button class="copy-btn" onclick="copyCode(this)">复制</button>
            <pre><code id="code1"><span class="cmd">mkdir</span> -p /etc/docker
<span class="cmd">vim</span> <span class="path">/etc/docker/daemon.json</span></code></pre>
        </div>

        <div class="section-title">2. 添加镜像源配置</div>
        <div class="code-wrapper">
            <button class="copy-btn" onclick="copyCode(this)">复制</button>
            <pre><code id="code2">{
  <span class="key">"registry-mirrors"</span>: [<span class="str">"https://hub.bravexist.cn"</span>]
}</code></pre>
        </div>

        <div class="section-title">3. 重启 Docker 服务</div>
        <div class="code-wrapper">
            <button class="copy-btn" onclick="copyCode(this)">复制</button>
            <pre><code id="code3"><span class="cmd">sudo</span> systemctl daemon-reload
<span class="cmd">sudo</span> systemctl restart docker</code></pre>
        </div>

        <footer>
            <div class="badges">
                <img src="https://up.bravexist.cn/api/badge/4/cert-exp" alt="Certificate Expiry">
                <img src="https://up.bravexist.cn/api/badge/4/status" alt="Status">
                <img src="https://up.bravexist.cn/api/badge/4/uptime" alt="Uptime">
            </div>
            <p class="footer-text">
                Powered by qiankong! &nbsp;|&nbsp; 
                <a href="https://bravexist.cn" target="_blank" class="footer-link">一世贪欢的私域</a>
            </p>
        </footer>
    </div>

    <script>
        function copyCode(btn) {
            // 获取当前按钮同级的 code 标签中的文本
            const codeBlock = btn.nextElementSibling.innerText;
            
            navigator.clipboard.writeText(codeBlock).then(() => {
                const originalText = btn.innerText;
                btn.innerText = '已复制!';
                btn.classList.add('copied');
                
                setTimeout(() => {
                    btn.innerText = originalText;
                    btn.classList.remove('copied');
                }, 2000);
            }).catch(err => {
                console.error('复制失败:', err);
                alert('复制失败,请手动复制');
            });
        }
    </script>
</body>
</html>

三、批判一下之前的解决方案

1.1 使用代理软件

利用本地的代理软件,配置SSH隧道,将代理的端口转发到需要使用访问的机器

ssh -L 7890:127.0.0.1:7890 root@remote_ip
export https_proxy=http://127.0.0.1:7897
http_proxy=http://127.0.0.1:7897
all_proxy=socks5://127.0.0.1:7897

有时可以临时解决问题,不过经常会遇到以下问题

  • Docker并没有成功走代理
  • 梯子的线路本身很差,
  • 流量少,带宽小(也受本地局域网的带宽限制)

1.2 使用公开的镜像源

使用公开的镜像源,固然很简单,不过依旧会遇到一些问题。体验过几个,效果都很一般。

  • 不付费,速度慢
  • IP地址莫名其妙被镜像源封禁
  • Docker有时候也不走代理
  • 镜像源早已失效(比如清华源等一系列知名镜像源,DeepSeek等AI太low了,还一直推荐)
  • 陆续有新的镜像源被墙

1.3 使用开源项目部署

固然也有一些开源项目可以自己部署,不过也还是会遇到问题

开源项目:

遇到的问题:

  • Docker有时候也不走代理
  • 有的开源项目使用CloudflareWorker ,可能被制裁
  • Fofa等网络空间搜索引擎抓取,为别人做嫁衣

四、镜像源汇总

当然总避免不了使用公开的镜像源,总之还是有一些价值的。

境内 Docker 镜像状态监控

五、封面图

封面图