用 Cloudflare Worker + R2 搭 Docker Hub 镜像加速站

2026年08月01日0 次阅读0 人喜欢
DockerCloudflareCF WorkerR2镜像加速部署
所属合集

VPC 部署博客,Docker 镜像拉不下来,EOF 报错一堆。折腾了一圈,最后用 Cloudflare Worker + R2 搞了个自己的镜像加速站。

出了什么事

博客的部署流程是 GitHub Actions 构建 Docker 镜像,推到 Docker Hub,然后 VPC 通过 SSH 执行 deploy.sh 拉取镜像并重启容器。

之前一直好好的,最近突然开始报 EOF:

复制代码
Error response from daemon: Head "https://registry-1.docker.io/v2/...": EOF

三次重试全挂,部署直接失败。

VPC 在国内,访问 Docker Hub 本来就不太稳定,这次是彻底不通了。

先试了几个公共镜像站

DaoCloud (docker.m.daocloud.io):白名单机制,只加速知名镜像。我的博客镜像 nnnnzs/react-nnnnzs-cn 不在白名单里,直接拒绝。

iyuu (hub.iyuu.cn):DNS 解析失败,站点可能已经挂了。

fnnas (docker.fnnas.com):飞牛的镜像加速站,居然能拉到。配置到 VPC 的 daemon.json 里,docker pull 走 fnnas,部署恢复了。

到这里博客部署的问题已经解决了。但我还想搞清楚一件事:我自己之前在 Cloudflare Workers 上部署过一个 Docker Hub 代理(docker-proxy.nnnnzs.cn),为什么它也报限流?

CF Worker 为什么被限流

看了下 Worker 代码,就是一个纯代理——每次请求都实时转发到 Docker Hub。问题在于 Cloudflare 的出口 IP 是共享的,Docker Hub 按 IP 段算限流配额(匿名用户 100 次/6 小时),CF 的 IP 被大量用户 share,配额很快就超了。

给 Worker 加 Docker Hub 认证

第一步是给 Worker 加上 Docker Hub 的 Personal Access Token。认证用户配额提升到 200 次/6 小时。

在 CF Workers 的 Secrets 里配了 DOCKERHUB_USERNAMEDOCKERHUB_PAT,Worker 在请求 Docker Hub 的 token 端点时带上 Basic Auth。

直接 curl 测试,token 端点返回的 x-ratelimit-limit: 3000,认证确实生效了。

但在 VPC 上 docker pull 还是报限流。

原因:Docker 的 mirror 协议设计如此——mirror 只代理 registry 请求,不代理 auth 请求。Docker client 用 mirror 拉镜像时,认证还是直接走 Docker Hub,不经过 Worker 的 /token 端点。所以 Worker 的认证加了等于没加。

上 R2 缓存层

既然认证走不通,那就做缓存。思路很简单:manifest 和 blob 数据存到 Cloudflare R2,大部分请求直接从 R2 返回,不打 Docker Hub。

Manifest(镜像元数据):tag 引用缓存 10 分钟,digest 引用缓存 24 小时。

Blob(layer 文件,镜像本体):永久缓存,内容寻址,同一个文件不会重复存储。

第一次拉取回源 Docker Hub,之后直接走 R2。R2 是 Cloudflare 自己的对象存储,不收出流量费,免费额度 10GB。

Worker 代码从 Service Worker 格式迁移到了 ES Module(R2 绑定需要),加了缓存逻辑。大文件(>4.5GB)跳过缓存,客户端拿到响应后异步写 R2,不阻塞下载。

VPC 上测试 docker pull docker-proxy.nnnnzs.cn/library/nginx:alpine,第二次请求返回 X-Cache: HIT

VPC 镜像配置

最终 VPC 的 daemon.json:

json 复制代码
{
  "registry-mirrors": [
    "https://docker-proxy.nnnnzs.cn",
    "https://docker.fnnas.com",
    "https://docker.m.daocloud.io"
  ]
}

自己 Worker 优先,fnnas 做 fallback,DaoCloud 兜底。

R2 清理

R2 的 blob 是永久存储的,不清理会慢慢涨。加了个生命周期规则:

bash 复制代码
wrangler r2 bucket lifecycle add docker-hub-cache   --name expire-old-blobs   --prefix blobs/   --expire-days 30

超过 30 天没访问的 blob 自动删除。manifest 本身有短 TTL,不需要额外处理。

代码仓库

Worker 代码在 GitHub 私有仓库 NNNNzs/docker-proxy-worker,用 wrangler 部署。

整个方案的依赖:Cloudflare Workers(免费)+ R2(免费额度内)+ Docker Hub PAT(免费)。没有额外成本。

加载评论中...