# docker-proxy **Repository Path**: zhReimu/docker-proxy ## Basic Information - **Project Name**: docker-proxy - **Description**: docker-proxy - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-03-28 - **Last Updated**: 2026-04-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Docker Proxy Docker Proxy 是一个使用 Go 构建的 Docker Registry 代理服务,内置一个以 React + Vite 实现的中文管理后台。前端构建产物会嵌入到 Go 二进制中,因此启动单个服务后即可同时提供以下入口: - `/admin`:后台管理界面 - `/api/admin/*`:后台管理 API - `/token`:Registry Bearer Token 签发入口 - `/v2/*`:Docker Registry 入口 - `/healthz`:健康检查 - `/version`:版本信息 整个系统的运行时配置存放在 SQLite 中。服务启动时会自动迁移数据库、引导默认资源、装配当前 Registry 运行时,并把构建后的管理后台一起对外提供。 ## 核心能力 - Registry 代理与 Bearer 鉴权:`/v2/*` 走标准 Docker Registry Bearer challenge 流程,`/token` 负责签发 Ed25519 JWT - 嵌入式管理后台:React 单页应用构建后嵌入 Go 二进制,通过 `/admin` 和 `/api/admin/*` 提供运维控制面 - 启动引导:首次启动自动初始化管理员账号、默认存储后端、默认上游、系统内置的 Docker Hub 代理和基础系统设置 - 存储后端管理:支持 `filesystem`、`s3` 与 `webdav` 三类后端,允许切换活跃后端并在进程内热重载;`webdav` 使用远端存储保存最终数据,并用本地暂存目录承载上传中的 `_uploads` - 上游与网络:管理远端上游仓库、HTTP/SOCKS5 代理,并支持为上游批量绑定代理;同时内置一个仅供 Docker Hub 使用的系统代理策略 - Host 路由:按 `priority ASC, id ASC` 顺序使用正则表达式匹配 Host,并将请求路由到目标上游 - 镜像管理:支持镜像索引重建、仓库与 Tag 检索、从上游拉取、重打 Tag、删除 Tag、删除 Manifest、批量删仓库、标记仓库私有性 - 运行时观测:查看初始化状态、当前版本、Go 运行时指标、活跃存储后端、系统设置和健康检查 - 日志输出:支持通过 flag 或环境变量设置日志级别,也支持额外写入 JSON 格式日志文件 ## 技术栈 - 后端:Go `1.25.6`、Chi、GORM、`glebarez/sqlite`、`distribution/distribution` - 前端:React `19`、TypeScript `5`、Vite `8`、React Router、Tailwind CSS `4` - 鉴权与安全:Basic Auth、Docker Registry Token、Ed25519、JWKS - 构建与交付:`go-task`、`pnpm`、Docker 多阶段构建、Go `embed.FS` ## 目录结构 - `cmd/server`:程序入口,负责参数解析、依赖装配与 HTTP 服务启动 - `internal/application`:应用服务层,编排引导、后台管理、鉴权、镜像操作与 Registry 相关用例 - `internal/domain`:领域模型与核心业务类型 - `internal/infrastructure/http`:HTTP 路由、认证中间件、嵌入式后台页面与接口适配 - `internal/infrastructure/persistence`:SQLite 持久化、系统设置、镜像索引与访问策略 - `internal/infrastructure/distribution`:Docker Distribution handler 装配、上游联邦中间件与热重载 - `internal/infrastructure/registryauth`:Registry Token 运行时、JWK/JWKS、签发与校验 - `internal/infrastructure/storage`:存储驱动工厂,当前支持 `filesystem`、`s3` 与 `webdav` - `web/admin`:管理后台前端源码;`web/admin/dist` 为生成物,由 [`web/admin/embed.go`](./web/admin/embed.go) 嵌入 ## 运行要求 - Go `1.25.6` - Node.js `22` - `pnpm` - `go-task` - Docker(仅在使用容器构建/运行时需要) 建议先启用 Corepack: ```bash corepack enable ``` ## 快速开始 首次拉起项目时建议先准备依赖: ```bash task deps task frontend-deps ``` 本地开发启动: ```bash task dev ``` 默认访问地址: - 后台管理:`http://127.0.0.1:8080/admin` - 健康检查:`http://127.0.0.1:8080/healthz` - 版本信息:`http://127.0.0.1:8080/version` - Registry 入口:`http://127.0.0.1:8080/v2/` - Token 服务:`http://127.0.0.1:8080/token` 说明: - `task dev` 会先构建一次嵌入式前端,再以 `go run ./cmd/server -db data/docker-proxy.db` 启动后端 - 如果需要前端热更新,请配合 [`web/admin/README.md`](./web/admin/README.md) 使用 `pnpm --dir web/admin dev` - 服务本身不做 TLS 终止;生产环境通常需要放在 HTTPS 反向代理之后,或为 Docker 客户端显式配置可信的 HTTP Registry ## 常用命令 - `task dev`:构建前端并以开发模式运行服务 - `task build`:下载依赖、构建前端并编译当前平台产物到 `dist/` - `task run`:基于构建产物运行服务 - `task test`:执行前端构建、前端 lint 与 `go test -v ./...` - `task fmt`:运行 `go fmt ./...` - `task clean`:清理 Go 构建缓存与 `dist/` - `task build-all`:构建多平台产物 - `task docker-build`:基于仓库内 `Dockerfile` 构建容器镜像 - `task docker-run`:按示例参数启动容器 如果只调试前端,可直接使用: ```bash pnpm --dir web/admin dev pnpm --dir web/admin build pnpm --dir web/admin lint ``` ## CLI 与环境变量 ### CLI 参数 当前二进制支持以下参数: - `--listen` / `-l`:监听地址,默认 `:8080` - `--db` / `-d`:SQLite 数据库路径,默认 `data/docker-proxy.db` - `--log-level`:日志等级,支持 `debug`、`info`、`warn`、`error` - `--log-file`:日志文件路径;配置后标准错误输出继续使用文本日志,文件额外写入 JSON 日志 - `--version` / `-v`:输出版本信息并退出 示例: ```bash ./dist/docker-proxy --listen :8080 --db data/docker-proxy.db --log-level debug ``` Windows 下通常为: ```powershell .\dist\docker-proxy.exe --listen :8080 --db data/docker-proxy.db --log-level debug ``` ### 环境变量 - `BOOTSTRAP_ADMIN_USERNAME`:首次初始化时的管理员用户名 - `BOOTSTRAP_ADMIN_PASSWORD`:首次初始化时的管理员密码 - `BOOTSTRAP_STORAGE_ROOT`:默认 `filesystem` 存储根目录,默认 `data/registry` - `BOOTSTRAP_HOST_ROUTES_JSON`:首次初始化时导入的 Host 路由 JSON 数组 - `DOCKER_PROXY_REGISTRY_TOKEN_PRIVATE_KEY_PEM`:Registry Token 服务使用的 Ed25519 私钥,兼容 PKCS8 PEM 与 base64 编码的 32 字节 seed - `DOCKER_PROXY_LOG_LEVEL`:默认日志等级;当 `--log-level` 也存在时,flag 优先 约束说明: - `BOOTSTRAP_ADMIN_USERNAME` 和 `BOOTSTRAP_ADMIN_PASSWORD` 必须同时提供,不能只设置其中一个 - 两者都不设置时,首次初始化会回落到默认管理员 `admin/admin` - `BOOTSTRAP_HOST_ROUTES_JSON` 仅在 Host 路由表为空时参与初始化 - `DOCKER_PROXY_REGISTRY_TOKEN_PRIVATE_KEY_PEM` 如未设置,服务会生成进程内临时密钥;服务重启后旧 Bearer Token 会失效 - `DOCKER_PROXY_REGISTRY_TOKEN_PRIVATE_KEY_PEM` 必须是 PKCS8 PEM 编码的 Ed25519 私钥,或 base64 编码的 32 字节 Ed25519 seed,否则服务会启动失败 - `DOCKER_PROXY_LOG_LEVEL` 支持 `debug`、`info`、`warn`、`error` 示例: ```bash BOOTSTRAP_ADMIN_USERNAME=admin \ BOOTSTRAP_ADMIN_PASSWORD=strong-password \ BOOTSTRAP_STORAGE_ROOT=data/registry \ DOCKER_PROXY_LOG_LEVEL=info \ ./dist/docker-proxy --listen :8080 --db data/docker-proxy.db ``` #### 生成 Registry Token 私钥 虽然环境变量名保留为 `DOCKER_PROXY_REGISTRY_TOKEN_PRIVATE_KEY_PEM`,但当前支持两种格式: 1. PKCS8 PEM 编码的 Ed25519 私钥 2. base64 编码的 32 字节 Ed25519 seed PEM 方式适合挂载文件或 Secret 文件: ```bash openssl genpkey -algorithm Ed25519 -out registry-token-key.pem export DOCKER_PROXY_REGISTRY_TOKEN_PRIVATE_KEY_PEM="$(cat registry-token-key.pem)" ``` ```powershell openssl genpkey -algorithm Ed25519 -out registry-token-key.pem $env:DOCKER_PROXY_REGISTRY_TOKEN_PRIVATE_KEY_PEM = Get-Content -Raw -Encoding UTF8 .\registry-token-key.pem ``` base64 seed 是单行字符串,更适合直接写环境变量、Windows `set` 或 CI Secret: ```bash openssl rand -base64 32 export DOCKER_PROXY_REGISTRY_TOKEN_PRIVATE_KEY_PEM='上一步输出的单行 seed' ``` ```cmd set DOCKER_PROXY_REGISTRY_TOKEN_PRIVATE_KEY_PEM=上一步输出的单行 seed ``` 使用 base64 seed 时,服务会在启动时把它扩展成完整的 Ed25519 私钥;只要各实例使用同一个 seed,就会得到同一把签名密钥。 Host 路由初始化示例: ```bash BOOTSTRAP_HOST_ROUTES_JSON='[ {"name":"dockerhub-route","priority":10,"host_regex":"^docker\\.example\\.com$","upstream":"dockerhub"}, {"name":"ghcr-route","priority":20,"host_regex":"^ghcr\\.example\\.com$","upstream":"ghcr"} ]' ``` 其中 `upstream` 需要引用数据库中存在的上游名称;首次启动默认会创建 `dockerhub` 和 `ghcr` 两个上游。 ## 默认初始化行为 当数据库为空或相关表尚未初始化时,服务会自动完成以下动作: - 创建默认管理员账号:`admin/admin` - 创建默认活跃存储后端:`default-filesystem` - 默认 `filesystem` 根目录为 `data/registry`,可被 `BOOTSTRAP_STORAGE_ROOT` 覆盖 - 创建系统内置代理:`dockerhub-builtin` - 创建两个默认启用的上游: - `dockerhub` -> `https://registry-1.docker.io` - `ghcr` -> `https://ghcr.io` - 写入基础系统设置: - `schema.version` - `bootstrap.completed` - `storage.reload_required` - 如提供 `BOOTSTRAP_HOST_ROUTES_JSON`,会在 Host 路由表为空时导入初始规则 说明: - `dockerhub-builtin` 只会在启动时自动创建,不会自动改写任何现有上游绑定 - 该代理是系统内置资源,不能删除、不能改名或改类型,但可以在后台编辑完整的 Caddy JSON 配置 - 保存 `dockerhub-builtin` 配置后只会更新数据库并标记 `storage.reload_required=true`,仍需手动重载运行时或重启服务后生效 - 该代理仅允许绑定到 Docker Hub 上游(`docker.io`、`index.docker.io`、`registry.docker.io`、`registry-1.docker.io`) 默认管理员仅适合本地开发。正式环境请在第一次启动前显式设置自己的初始化凭据。 ## 请求入口与鉴权 ### 管理后台 - `/admin` 与 `/api/admin/*` 使用 Basic Auth - 仅 `admin` 角色可以访问管理后台 - 没有独立登录页,浏览器会直接触发 Basic Auth 认证流程 - 当前会话信息可通过 `/api/admin/users/me` 获取 ### Registry 与 Token - `/v2/*` 只接受 Bearer Token,不支持直接对 `/v2/*` 发送 Basic Auth 完成访问 - `/token` 要求 `service=docker-proxy-registry` - `/token` 可以匿名访问,也可以携带 Basic Auth;携带凭证时允许 `admin` 与 `registry` 角色申请完整 scope - Token 由服务端使用 Ed25519 签发,默认有效期为 300 秒 - 匿名请求只能拿到公开仓库 `pull` scope 的交集裁剪结果 - 被标记为私有的镜像仓库需要先登录并获取合法 scope 才能访问 - `registry:catalog:*` 仅对已登录的 `admin` / `registry` 角色开放 匿名拉取公开仓库时,请求过程大致如下: ```text GET /v2//manifests/ -> 401 WWW-Authenticate: Bearer realm="https:///token",service="docker-proxy-registry",scope="repository::pull" GET /token?service=docker-proxy-registry&scope=repository::pull -> 200 { "token": "...", "access_token": "...", ... } GET /v2//manifests/ Authorization: Bearer -> 200 ``` 登录后可使用 Docker CLI: ```bash docker login docker pull /: ``` ## 管理后台 API 概览 后台 API 主要分为以下几组: - 启动与运行时:`/bootstrap/status`、`/system-info`、`/runtime`、`/dashboard` - 系统配置:`/settings` - 存储管理:`/storage-backends`、`/storage-backends/reload` - 网络资源:`/proxies`、`/upstreams`、`/upstreams/bulk-proxy` - 路由与账号:`/host-routes`、`/users`、`/users/me` - 镜像管理:`/images/repositories`、`/images/tags`、`/images/manifests/impact`、`/images/pull`、`/images/index/rebuild` 前端与这些接口的契约位于 [`web/admin/src/domain`](./web/admin/src/domain) 与 [`web/admin/src/infrastructure/http/admin`](./web/admin/src/infrastructure/http/admin)。 ## WebDAV 存储后端 `webdav` 后端通过管理后台的存储后端表单进行配置,当前支持以下配置键: - `baseurl`:必填,WebDAV 服务地址,必须是绝对 `http/https` URL - `rootdirectory`:可选,远端路径前缀,默认 `/` - `username` / `password`:可选,Basic Auth 凭据 - `headers`:可选,自定义请求 Header;如果显式提供 `Authorization`,会覆盖由用户名密码生成的 Basic Auth - `insecureskipverify`:可选,默认 `false`;启用后会跳过 TLS 证书校验 - `spooldirectory`:可选,默认 `data/webdav-spool`;用于保存上传中的 `_uploads` 与 hash state 实现约束说明: - 最终 Registry 数据会落到远端 WebDAV - 上传中的 `_uploads`、断点续传状态和哈希状态文件会写到本地 `spooldirectory` - 激活或修改 `webdav` 配置后,仍然需要通过后台“重载存储”或重启服务使运行时生效 ## Registry 运行时与热重载 服务启动时会按以下流程装配 Registry 运行时: 1. 打开 SQLite 数据库并执行自动迁移 2. 读取引导环境变量,必要时写入默认管理员、默认存储、默认上游和初始 Host 路由 3. 初始化 Registry Token 运行时,准备签名密钥与 JWKS 4. 从数据库读取活跃存储后端、上游、Host 路由和系统设置 5. 构建当前存储驱动并创建 Docker Distribution handler 6. 编译 Host 路由正则并绑定联邦中间件 7. 将 `/token` 与 `/v2/*` 接到同一套 Token runtime 8. 启动 HTTP 服务并暴露嵌入式后台页面 以下变更会把系统设置 `storage.reload_required` 置为 `true`: - 存储后端新增、更新、删除 - 代理新增、更新、删除 - 上游新增、更新、删除、批量应用代理 - Host 路由新增、更新、删除 - 系统设置更新 - 首次初始化时发生的运行时配置写入 手动重载入口: ```http POST /api/admin/storage-backends/reload ``` 行为说明: - 重载成功后会在当前进程内重新构建 Registry 运行时 - 成功后会清除 `storage.reload_required` - 成功后会把镜像索引状态标记为过期,等待重新建立索引 - 如果重载失败,旧运行时保持不变,`storage.reload_required` 会继续保持为 `true` - 服务重启时也会按数据库中的最新配置重新装配运行时 - 如把上游切换到 `dockerhub-builtin`,仍需按现有流程执行“重载存储”或重启服务,新的 Registry 运行时才会生效 ## 后端实现概览 项目后端按 DDD 风格分层组织: - `internal/domain` - `iam`:账号、角色与权限模型 - `settings`:存储后端、代理、上游、Host 路由与系统设置 - `registry`:Host 归一化与上游解析结果等核心类型 - `internal/application` - `bootstrap`:读取环境变量并完成首次引导 - `iam`:密码校验、Basic Auth 鉴权、最后登录时间更新 - `admin`:后台 API 用例、镜像管理、隐私策略、索引重建 - `registry`:Host 路由解析器与上游客户端抽象 - `internal/infrastructure` - `persistence`:SQLite 表模型、查询与事务 - `distribution`:Distribution handler、联邦中间件与热重载 - `http`:管理后台 API、Registry 入口、认证中间件与嵌入式 UI - `registryauth`:Registry Token 运行时与签名 - `storage`:Distribution 存储驱动工厂 其中几个关键点: - 配置不是写在静态 YAML 中,而是以数据库记录的形式驱动运行时 - 镜像索引与仓库私有性也落在 SQLite 中,便于后台分页检索和权限裁剪 - 前端并不是独立部署应用,而是 `web/admin/dist` 嵌入服务后随二进制一起发布 ## 容器构建 仓库内 `Dockerfile` 使用多阶段构建: 1. 基于 Node.js 22 构建前端 2. 基于 Go 1.25 编译 Linux `amd64` 二进制 3. 使用 distroless 运行时镜像发布最终产物 镜像默认暴露 `8080` 端口,入口命令为: ```text /app/docker-proxy ``` ## 开发约束 - 优先使用 `task` 统一执行依赖安装、构建、测试与 Docker 命令 - 不要手工修改 `web/admin/dist` - 后台前端的部署根路径固定为 `/admin` - 前端产物通过 [`web/admin/embed.go`](./web/admin/embed.go) 嵌入到 Go 服务 - 新的领域规则应放在 `internal/domain` 与 `internal/application`,不要在 `internal/infrastructure` 中直接堆业务逻辑