搭建轻量级 Docker 代理
在日常开发中,经常会遇到 Docker 拉取镜像缓慢、连接不稳定、某些仓库国内访问困难等问题。为了解决这些实际痛点,我实现重复造轮子了一个轻量级 Docker Registry 反向代理,用于中转和加速 Docker 客户端的镜像拉取请求。项目采用 Python + FastAPI 编写,实现了对多个主流仓库的透明代理。
本文将介绍实现背景、设计思路、功能特点以及代码结构,最后提供开源地址,供需要的用户部署使用。
背景
Docker 官方 Registry 对网络质量要求较高,镜像层往往较大。很多场景下,直接从 DockerHub 或 GHCR 拉取镜像会遇到超时、速率不稳定、DNS 污染、IP 限速等问题。
市面上已经有一些现成的镜像代理服务,例如 Docker 官方提供的实现了 OCI Distribution spec 的 registry,但是这样的服务配置复杂,资源要求高,难以在云服务器等环境上部署。在一些轻量级环境中,更希望使用一个灵活性更高、资源占用低的代理。
于是就有了一个目标:
写一个尽量小、足够稳定、可自部署的 Docker Registry 代理,同时保持兼容性。
设计目标
实现这个代理时,我对系统设计目标做了一些规划:
- 兼容 Docker Registry API v2
必须完整支持:
- GET
/v2/ - GET
/v2/auth - 获取 manifest / tag / blob 等路径
- DockerHub 的 library 自动补全规则
秘诀在于遵循 Docker 客户端的认证流程,模拟 DockerHub 的 WWW-Authenticate 行为。
- 流式转发以降低内存消耗
原始实现如果直接使用 resp.content,会导致 Python 在内存中加载整个 blob,动辄几百兆。这在生产环境是不可接受的。
最终代理采用 StreamingResponse,将上游的内容以流式方式直接转发,避免占用大量内存。
- 支持并发请求
使用全局 httpx.AsyncClient,依赖其内部连接池来处理多并发,加上 FastAPI 的异步模型,在单 worker 的情况下也能处理多条镜像拉取流水线。
- 支持多个仓库
例如:
- DockerHub
- GHCR
- Quay
- Google Container Registry
- Kubernetes Registry
这些仓库的 API 大致一致,只需要按不同域名路由到不同的上游即可。
整体流程
代理的请求处理流程如下:
- 客户端访问
docker.example.com/v2/... - 代理根据主机名判断应转发至哪个镜像源
/v2/与/v2/auth按 Docker 协议处理认证- repository 路径按需要补全
library/ - 其它请求以 streaming 方式转发
- 对 DockerHub 的 blob 307 跳转进行手动跟随
- 将上游响应回传给 Docker 客户端
整个过程对客户端透明,使其可以像访问官方仓库一样访问自定义域名。
代码结构
项目核心的 streaming 实现基于下面的模式:1
2
3
4
5
6
7
8
9req = client.build_request("GET", upstream_url)
upstream_resp = await client.send(req, stream=True)
return StreamingResponse(
upstream_resp.aiter_bytes(),
status_code=upstream_resp.status_code,
headers=response_headers,
background=BackgroundTask(upstream_resp.aclose),
)
开源地址
项目已在 GitHub 开源:
https://github.com/web-llm/fast-docker-proxy
如果你需要一个可控、轻量、可扩展的 Docker Registry 代理,可以直接基于该项目部署,也欢迎提交 issue 或 PR。具体使用方法请参考仓库的 README 文档。