Skip to content
RainlvanPublic

About

基于 project-zot/zot 的 Docker 镜像加速二次开发,附轻量公网/私有模式管理页

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Zot Docker Hub 镜像加速与独立管理页

Warning

本仓库是基于 project-zot/zot 的二次开发, 是非 Zot 官方发行版。当前发行版固定上游 Zot v2.1.20。本项目不能保证跨 Zot 大版本 时配置格式永远兼容;升级前必须备份并完成独立环境验证。

这个项目把 Zot 配置成 Docker Hub 的按需缓存镜像,并增加一个独立、轻量的管理服务。 管理员可以在 Web 页面中选择:

  • 公网匿名镜像加速:默认模式。Docker 客户端可把站点设为 registry-mirrors,继续用 docker pull hello-world:linux 这类普通镜像名。
  • 私有镜像加速:匿名访问返回 401。客户端登录后,使用完整站点域名显式拉取,例如 docker pull registry.example.com/library/hello-world:linux。

生产镜像:ghcr.io/rainlvan/zot:2.1.20-r1

2.1.20-r1 是推荐的固定标签;2.1.20 和 latest 是方便标签,latest 不建议用于生产环境。

与上游 Zot 的关系

本项目直接使用上游 Zot v2.1.20 的完整可执行文件,保留 Zot 的 OCI Registry、Web UI、 搜索和 Sync 扩展能力。新增部分主要是:

  • 同一个镜像中的严格入口点、幂等初始化器和健康检查;
  • 独立运行的 mirror-admin 管理服务;
  • 公网/私有模式的事务式切换控制器;
  • Docker Compose、1Panel、Nginx、Caddy 和 TLS/HTTPS 示例;
  • GitHub Actions 双架构构建与 GHCR 发布;
  • 面向部署者的安全、兼容性、备份和回滚说明。

上游项目、许可证和 NOTICE 归属见 NOTICE。本仓库不是 project-zot 团队维护的 分支,也不代表上游对这里的管理服务、镜像或部署方式提供支持。

轻量管理服务的原理和好处

管理页没有引入数据库、Node.js 运行时或额外应用框架。它使用 Python 标准库提供静态页面 和很小的 JSON API,只保存当前结果、私有访问策略和配置备份。Zot 与管理服务是两个独立 容器,但来自同一个固定镜像,因此:

  • Zot 和管理服务可以分别查看日志、健康状态与重启;
  • 管理服务故障不会替代 Zot 的数据目录;
  • 发布物只有一个镜像,版本与回滚关系清楚;
  • 配置、凭据、状态和 registry 数据全部保存在宿主机的 ZOT_ROOT;
  • 管理 API 只接受反向代理注入的 64 位十六进制密钥,浏览器拿不到该密钥。

管理服务需要挂载 Docker socket 来校验候选配置并重启 Zot。即使 Compose 中写成只读挂载, Docker socket 仍应视为接近宿主机 root 的权限。不要运行未经审查的管理镜像,不要把 5001 直接开放到公网。

配置切换事务

每次切换都执行以下流程:

  1. 读取现有配置并只修改模式相关字段;
  2. 写入同目录的候选配置;
  3. 使用目标 Zot 镜像执行 zot verify 验证候选配置;
  4. 备份当前配置并原子替换;
  5. 重启 Zot,等待容器健康;
  6. 探测 /v2/,公网模式必须为 200,私有模式必须为 401;
  7. 任一步失败则回滚旧配置并再次确认健康。

这种“先验证候选配置,再切换,失败则回滚”的方式能降低配置错误导致长时间中断的风险, 但不能保证跨大版本兼容,也不能替代数据备份和升级演练。详细边界见 兼容性说明。

部署前准备

你需要:

  • 一台安装 Docker Engine 与 Docker Compose v2 的 Linux 服务器;
  • 一个解析到服务器公网 IP 的域名,例如 registry.example.com;
  • 公网只开放 TCP 80/443;
  • 能从服务器访问 Docker Hub、GHCR 和证书 ACME 服务;
  • 足够的磁盘空间保存缓存层和你的私有镜像。

不要在安全组或防火墙中开放 5000/5001。Compose 只把它们绑定为 127.0.0.1:5000 和 127.0.0.1:5001,TLS 由同机反向代理终止。

以下命令假定:

  • 部署根目录是 /opt/zot;
  • 示例域名是 registry.example.com;
  • 你已把本仓库克隆到 /opt/zot-src。

请把示例域名替换成自己的域名。

Docker Compose 快速部署

1. 准备部署文件

sudo install -d -m 700 /opt/zot
sudo install -m 644 /opt/zot-src/compose.yaml /opt/zot/compose.yaml
sudo install -m 600 /opt/zot-src/.env.example /opt/zot/.env
DOMAIN=your-registry.example.net
sudo sed -i "s|registry.example.com|$DOMAIN|g" /opt/zot/.env

上面的 sed 左侧是仓库示例值,右侧应改成你的真实域名。确认固定镜像与目录:

sudo grep -E '^(ZOT_IMAGE|ZOT_ROOT|ZOT_PUBLIC_ORIGIN)=' /opt/zot/.env

预期包含:

ZOT_IMAGE=ghcr.io/rainlvan/zot:2.1.20-r1
ZOT_ROOT=/opt/zot
ZOT_PUBLIC_ORIGIN=https://registry.example.com

2. 先初始化,再启动

显式运行初始化可以确保 Zot 容器创建前,配置和 htpasswd 文件已经存在:

cd /opt/zot
sudo docker compose pull
sudo docker compose run --rm init
sudo docker compose up -d
sudo docker compose ps

初始化器可重复运行。它会保留所有非空配置、凭据、管理状态和数据,不会把已有文件重置为 默认值。

3. 首次管理员密码

没有提供 Docker secret 时,初始化器会生成一次性明文交接文件:

sudo cat /opt/zot/mirror-admin/state/initial-admin-password

用户名是 zotadmin。把密码保存到密码管理器并验证登录后,删除明文交接文件:

sudo rm /opt/zot/mirror-admin/state/initial-admin-password

删除它不会删除 htpasswd 中的 bcrypt 哈希。不要把初始密码、.env、htpasswd、代理密钥或 任何 TLS 私钥提交到 Git。

4. 持久目录说明

路径 内容 是否必须备份
/opt/zot/compose.yaml、.env 部署版本与环境 是
/opt/zot/config/ Zot 配置与 htpasswd 是
/opt/zot/data/ 缓存镜像、私有镜像、元数据 是
/opt/zot/mirror-admin/state/ 代理密钥、模式状态、配置备份 是

1Panel:建站、申请证书和启用 HTTPS

这一节中的证书申请必须通过 1Panel 左侧导航完成,不是在 1Panel 网页终端里运行 ACME 命令。官方入口是 网站 → 证书。

1. 创建反向代理网站

  1. 打开 1Panel 左侧 网站,进入网站列表。
  2. 点击创建网站,选择 反向代理。
  3. 主域名填写 registry.example.com。
  4. 代理地址填写 http://127.0.0.1:5000。
  5. 创建后先保持后端为 HTTP;Zot 的 5000 端口不对公网开放。

1Panel 生成的 / 反向代理负责 Registry API 和 Zot UI。大镜像上传/下载需要关闭请求与 响应缓冲,并适当提高超时;仓库提供的 Nginx/OpenResty 示例已经包含这些设置。

2. 在“网站 → 证书”申请证书

  1. 在左侧进入 网站 → 证书。
  2. 首次使用先创建 ACME 账户,填写有效邮箱并同意 CA 条款。
  3. DNS 托管商支持 API 时,先创建 DNS 账户,再选择 DNS 验证;单域名也可按 1Panel 页面 提示使用 HTTP 验证。
  4. 点击申请证书,主域名填写 registry.example.com,选择刚创建的 ACME/DNS 账户。
  5. 申请成功后确认状态正常、到期时间正确,并开启自动续签。
  6. 回到该网站的 HTTPS 设置,启用 HTTPS,选择刚申请的证书,并启用 HTTP 跳转 HTTPS。

若使用 HTTP 验证,公网 80 必须能到达 1Panel/OpenResty;DNS 必须已经指向服务器。证书 下载包可能含私钥,只能通过安全渠道保存。

3. 加入受保护的独立管理页

先把同一个管理员 bcrypt 文件放到 1Panel 网站目录:

sudo install -D -m 600 \
  /opt/zot/config/htpasswd \
  /www/sites/zot/.mirror-admin-htpasswd

再用仓库脚本把内部代理密钥写入只允许 root 读取的配置片段。脚本只输出目标路径,不输出 密钥内容:

cd /opt/zot-src
sudo ZOT_ROOT=/opt/zot ./scripts/render_proxy_config.sh \
  deploy/1panel-locations.conf.template \
  /www/sites/zot/mirror-admin-locations.conf

在 1Panel 的网站配置页面编辑该站点的 OpenResty/Nginx 配置,在 HTTPS server { ... } 内部加入:

include /www/sites/zot/mirror-admin-locations.conf;

保存前使用 1Panel 页面提供的配置检查;保存后让 1Panel 重载网站配置。不要把包含代理密钥 的渲染结果粘贴到 Issue、聊天或仓库。

最终访问:

https://registry.example.com/_mirror-admin/

浏览器先显示 Basic Auth,用户名为 zotadmin。通过后才能看到简单的公网/私有模式选项。

4. 1Panel 验证

curl -i https://registry.example.com/v2/
curl -I https://registry.example.com/_mirror-admin/

默认公网模式下 /v2/ 应返回 200;管理路径未提供 Basic Auth 时应返回 401。若返回 502, 先在 1Panel 宿主机确认 127.0.0.1:5000 和 127.0.0.1:5001 正在监听。

1Panel 官方文档:

普通 Linux + Nginx TLS

仓库的 Nginx 示例包含:80 端口 ACME/HTTPS 跳转、TLS 1.2/1.3、 Registry 无限请求体、900 秒超时、关闭代理缓冲,以及受 Basic Auth 保护的管理路径。

  1. 把示例复制到 Nginx 配置目录并替换域名与证书路径。
  2. 使用渲染脚本替换 @@PROXY_SECRET@@:
cd /opt/zot-src
sudo ZOT_ROOT=/opt/zot ./scripts/render_proxy_config.sh \
  deploy/nginx.conf.example \
  /etc/nginx/conf.d/zot.conf
sudo nginx -t
sudo systemctl reload nginx
  1. 确认 /etc/nginx/conf.d/zot.conf 权限为 0600,且 Nginx master 能读取 /opt/zot/config/htpasswd。
  2. 证书可由 certbot、发行版 ACME 工具或已有企业 CA 提供。不要在公网使用自签证书, 除非所有客户端都已正确安装私有 CA。

如果 Nginx 本身运行在容器中,127.0.0.1 指向 Nginx 容器,而不是宿主机;应使用受控的 Docker 网络或明确的宿主机网关地址,并继续保证 5000/5001 不直接暴露公网。

普通 Linux + Caddy 自动 HTTPS

Caddy 示例使用域名触发自动 HTTPS,并给管理路径增加 basic_auth 与内部代理密钥。Caddy 需要公网 80/443 且 DNS 已生效。

  1. 复制并编辑域名:
sudo install -m 644 /opt/zot-src/deploy/Caddyfile.example /etc/caddy/Caddyfile
DOMAIN=your-registry.example.net
sudo sed -i "s/registry.example.com/$DOMAIN/g" /etc/caddy/Caddyfile
  1. 交互式生成 Caddy 需要的密码哈希,不要在命令参数中写明文密码:
sudo caddy hash-password
  1. 创建 root-only 的 /etc/caddy/zot.env,写入两行:
CADDY_ADMIN_HASH=上一步输出的哈希
ZOT_PROXY_SECRET=从/opt/zot/mirror-admin/state/proxy-secret读取的64位值
  1. 权限与 systemd 配置:
sudo chmod 600 /etc/caddy/zot.env
sudo systemctl edit caddy

在编辑器中加入:

[Service]
EnvironmentFile=/etc/caddy/zot.env

然后验证并重载:

sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl daemon-reload
sudo systemctl restart caddy

Caddy 使用真实域名时会自动申请和续签公网可信证书。官方说明见 Automatic HTTPS 和 reverse_proxy。

Docker 客户端使用

公网匿名镜像加速(默认)

在 Linux Docker Engine 的 /etc/docker/daemon.json 中加入:

{
  "registry-mirrors": ["https://registry.example.com"]
}

若文件已有其他配置,请合并 JSON,不要覆盖。验证并重启 Docker:

sudo dockerd --validate --config-file=/etc/docker/daemon.json
sudo systemctl restart docker
docker info | sed -n '/Registry Mirrors/,+3p'
docker pull hello-world:linux

Docker Desktop 用户在 Settings → Docker Engine 合并同一段 JSON,然后 Apply & Restart。

这里的 registry-mirrors 是 Docker Hub pull-through mirror。它不会自动代理任意第三方 registry。Docker 官方说明见 Mirror the Docker Hub library。

私有镜像加速

在管理页切换到私有模式后,不再依赖普通镜像名的匿名 mirror 流程。先登录,再使用完整 域名显式拉取:

docker login registry.example.com
docker pull registry.example.com/library/hello-world:linux

用户名默认是 zotadmin。使用 --password-stdin 可避免把密码写入命令历史:

printf '%s\n' "$ZOT_PASSWORD" | \
  docker login registry.example.com --username zotadmin --password-stdin

不要在共享 Shell 历史、CI 日志或 Compose 文件中保存明文密码。

管理页操作

打开 https://registry.example.com/_mirror-admin/。页面只提供当前模式、Zot 健康状态、最近 一次切换结果、数据量,以及两个简单选项。默认开启公网匿名镜像加速。

切换期间页面会暂时禁用按钮。成功返回前,控制器已经完成候选配置校验、备份、原子切换、 重启、健康检查与模式探测。若显示失败,当前请求会返回错误码,控制器会尝试恢复旧配置; 请查看日志确认回滚健康后再重试:

cd /opt/zot
sudo docker compose logs --tail=200 mirror-admin zot
sudo docker compose ps

包含数据的备份与恢复

只备份 Compose 文件不够。完整备份必须包含镜像缓存、私有镜像、Zot 配置、htpasswd、管理 状态、配置备份、Compose 和 .env。

一致性优先的离线备份

cd /opt/zot
sudo docker compose stop mirror-admin zot
sudo tar --acls --xattrs -C /opt \
  -czf /safe-backups/zot-$(date +%Y%m%d-%H%M%S).tar.gz zot
sudo docker compose start zot mirror-admin
sudo sha256sum /safe-backups/zot-*.tar.gz

备份期间 registry 暂停服务,但文件系统状态一致。在线文件复制可能跨越写入边界,只适合 你明确接受该一致性风险,或底层存储提供一致快照时使用。

恢复

  1. 在新服务器安装相同或已验证兼容的 Docker/Compose。
  2. 校验备份 SHA-256。
  3. 确认目标 /opt/zot 不包含需要保留的数据。
  4. 停止旧服务,把现有目录移动到带时间戳的保留目录,再解压备份。
  5. 先用固定镜像运行初始化/校验,再启动:
cd /opt/zot
sudo docker compose run --rm init
sudo docker compose up -d
sudo docker compose ps

恢复后分别验证公网或私有流程。不要在没有验证备份的情况下删除旧目录。

升级、回滚和卸载

同一 Zot 版本内升级项目修订

  1. 创建包含数据的完整备份。
  2. 把 .env 中 ZOT_IMAGE 改成明确的新修订标签,不要直接改为 latest。
  3. 在旧容器仍运行时拉取镜像并验证现有配置:
cd /opt/zot
sudo docker compose pull
sudo docker compose run --rm init
  1. 验证成功后执行 sudo docker compose up -d,再做公网和私有端到端测试。

回滚镜像

把 .env 的 ZOT_IMAGE 改回上一个固定标签,然后:

cd /opt/zot
sudo docker compose pull
sudo docker compose up -d
sudo docker compose ps

配置切换失败的自动回滚只恢复配置文件,不会自动回滚镜像版本或 registry 数据格式。跨 Zot 大版本回滚必须参考上游迁移说明和你自己的恢复演练。

卸载但保留数据

cd /opt/zot
sudo docker compose down

这会删除项目容器和网络,但保留 /opt/zot 下的绑定目录。删除 /opt/zot 会永久删除缓存、 私有镜像、配置和凭据;只有在已验证备份且确认不再需要数据时才手工执行。

常见问题

/v2/ 返回 502

sudo ss -lntp | grep -E '127\.0\.0\.1:(5000|5001)'
cd /opt/zot && sudo docker compose ps
cd /opt/zot && sudo docker compose logs --tail=200 zot mirror-admin

确认反向代理和 Docker 在同一宿主机;若代理在容器中,不要把容器内的 127.0.0.1 当作 宿主机。

管理页一直 401

  • 确认 Basic Auth 用户名是 zotadmin;
  • 确认反向代理读取的是当前 /opt/zot/config/htpasswd 副本;
  • 修改 Zot 密码后,同步更新 1Panel 的 .mirror-admin-htpasswd;
  • 不要删除代理注入的 X-Mirror-Proxy-Secret。

公网模式仍无法拉取 Docker Hub

  • 检查 Docker 客户端的 registry-mirrors 和 Docker daemon 重启结果;
  • 检查服务器能否访问 Docker Hub;
  • 查看 Zot 日志中的 Sync 错误、上游限流和磁盘空间;
  • 确认配置保留 onDemand: true、preserveDigest: true 和 http.compat: ["docker2s2"]。

Zot 官方配置说明明确指出:使用 preserveDigest: true 时必须同时启用 docker2s2,否则 Zot 会拒绝启动。参考 Configuring Zot。

上传大镜像失败或 413

确认 Nginx/OpenResty 有 client_max_body_size 0,并关闭 proxy_request_buffering/proxy_buffering。同时检查 CDN、WAF 或上游代理是否另有限制。

证书申请失败

  • DNS 必须解析到当前服务器;
  • HTTP 验证需要公网 80;
  • DNS 验证需要正确的 API 权限;
  • 在 1Panel 网站 → 证书 查看失败详情,而不是在网页终端重新创建一套独立 ACME 状态。

安全与兼容性

  • 安全报告与部署边界见 SECURITY.md。
  • 支持矩阵和大版本升级要求见 docs/compatibility.md。
  • 公网匿名服务应监控带宽、磁盘、上游限流和滥用;必要时加 CDN/WAF、限速或访问策略。
  • Docker socket 是高权限边界;管理端口不得公网直连。
  • TLS/HTTPS 只保护传输,不替代管理认证、强密码、主机加固和备份。

开发与验证

python -m unittest discover -s tests -v
python -m compileall -q mirror_admin container tests
bash -n container/*.sh scripts/*.sh ops/*.sh
docker build -t zot-project:dev .

Linux 端到端测试会创建唯一 /tmp 目录、唯一容器/网络/镜像,并在退出时做有界清理:

bash scripts/smoke_container.sh

成功时必须看到:

init_idempotent=ok
zot=healthy
mirror_admin=healthy
public_ordinary_pull=ok
private_anonymous=401
private_authenticated_explicit_pull=ok
final_mode=public
cleanup=ok

许可证与致谢

本项目使用 Apache License 2.0。感谢 project-zot/zot 提供生产级 OCI Registry 基础能力。 Zot 的安装与配置资料可参考 Zot v2.1.20 文档。

About

基于 project-zot/zot 的 Docker 镜像加速二次开发,附轻量公网/私有模式管理页

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages