Zot 2.1.22 Docker Compose 部署指南
在 Docker 服务器上用 Compose 部署 Zot 2.1.22:镜像选型、htpasswd 账号、推拉闭环、数据持久化、等价 docker run 与运维 FAQ,实测通过。
Zot 2.1.22 Docker Compose 部署指南
目标:在装有 Docker 的服务器上以 Docker Compose 部署 OCI 原生镜像仓库 Zot v2.1.22(project-zot/zot),覆盖镜像选型、编排文件与配置文件编写、htpasswd 账号生成、启动、API 与推拉闭环验证、数据持久化、等价 docker run 命令与日常运维。已在 Ubuntu 24.04.5 LTS(x86_64)+ Docker 29.9.2 + Docker Compose v5.6.1 环境实测通过(2026-11-01)。Zot 是 OCI 镜像仓库,与文献管理软件 Zotero 无关;选型背景见配套《Zot 介绍与选型指南》。
一、环境信息
本节给出本次部署的宿主环境与版本基准,文中全部命令与回显均产生于该环境。
| 项 | 取值 |
|---|---|
| 操作系统 | Ubuntu 24.04.5 LTS(x86_64) |
| Docker | 29.9.2(build a82e528) |
| Docker Compose | v5.6.1(docker compose 插件语法) |
| 应用镜像 | ghcr.io/project-zot/zot:v2.1.22(full 构建) |
| 端口规划 | 宿主机 8150 → 容器 5000(HTTP,未启用 TLS) |
| 编排目录 | /opt/data/quzuo/tmp/zot/stack(compose 与配置文件) |
| 数据目录 | /opt/data/quzuo/tmp/zot/data(挂载为容器 /var/lib/registry) |
| 账号 | admin / reader,口令均为 q1w2e3r4(bcrypt 散列存储) |
版本自检实测回显:
$ docker --version
Docker version 29.9.2, build a82e528
$ docker compose version
Docker Compose version v5.6.1
说明:文中部署目录为实验环境实际路径;生产环境可换成
/opt/zot之类的规划路径,同步修改编排文件中的挂载路径即可。
二、镜像选型说明
zot 官方在 GitHub Container Registry 发布多个镜像,本文采用 full 版:
| 镜像 | 内容 | 适用场景 |
|---|---|---|
ghcr.io/project-zot/zot | full 构建,含 search(CVE)、ui、sync、scrub、trust、metrics、lint、mgmt、userprefs 等全部扩展 | 需要 Web UI、搜索、同步、验签的私有仓库(本文) |
ghcr.io/project-zot/zot-minimal | 仅 dist-spec 核心代码与依赖 | 只要推拉镜像、追求最小攻击面 |
ghcr.io/project-zot/zxp | Prometheus exporter | 给 minimal 部署补指标 |
ghcr.io/project-zot/zb | 压测工具 | 容量规划 |
镜像拉取实测回显(本机已有镜像,显示 up to date):
$ docker pull ghcr.io/project-zot/zot:v2.1.22
v2.1.22: Pulling from project-zot/zot
Digest: sha256:96cda11459ce6f8c60da3b03f03e9f67fd11b3ae2d82c4c04c92b3c608996080
Status: Image is up to date for ghcr.io/project-zot/zot:v2.1.22
ghcr.io/project-zot/zot:v2.1.22
full 镜像内置一份默认配置 /etc/zot/config.json(实测从镜像文件系统导出)。不挂载自定义配置时,zot 就按它运行:
{
"storage":{
"rootDirectory":"/var/lib/registry"
},
"http":{
"address":"0.0.0.0",
"port":"5000",
"compat": ["docker2s2"]
},
"log":{
"level":"debug"
},
"extensions": {
"search": {
"enable": true,
"cve": {
"updateInterval": "2h"
}
},
"ui": {
"enable": true
},
"mgmt": {
"enable": true
}
}
}
要点:默认监听 0.0.0.0:5000,数据落在 /var/lib/registry,启用 search(含 CVE 扫描)、ui、mgmt 三个扩展,没有 auth 段(匿名可读可写),日志 debug 级输出到 stderr。生产使用必须像本文第四章那样挂载自己的配置补上认证。
💡 技术拓展:「镜像仓库」一词在 Docker 生态里有多个实现,容易与官方
registry镜像混淆,三者关系如下——
对象 镜像 定位 zot ghcr.io/project-zot/zotOCI 原生仓库,单二进制内置 UI/搜索/同步/验签 distribution(Docker Registry) registry:2协议最小实现,无 UI、无扩展 Harbor ghcr.io/harbor/harbor-*企业级平台,多组件(项目空间/RBAC/复制) 三者服务端都遵循 OCI Distribution Spec,docker、podman、skopeo 等客户端用法完全一致;本文选 zot,是为了用单容器同时获得协议服务 + Web UI + CVE 搜索。
三、前置条件
- 服务器已安装 Docker 与 Docker Compose v2 及以上(
docker compose version有回显,本文实测 v5.6.1); - 可访问
ghcr.io拉取镜像;网络不通时先为 Docker 守护进程配置代理或镜像加速; - 宿主机 8150 端口空闲(
ss -ltn | grep 8150应无输出;被占用则换端口并同步修改编排文件,见第十一章 FAQ); - 创建编排与数据目录:
mkdir -p /opt/data/quzuo/tmp/zot/stack /opt/data/quzuo/tmp/zot/data
四、部署步骤
4.1 准备编排文件、配置与账号文件
在 /opt/data/quzuo/tmp/zot/stack/ 下创建三个文件。
docker-compose.yml(全文,与盘上实际文件一致):
name: zot-2.1.22-stack
services:
zot:
image: ghcr.io/project-zot/zot:v2.1.22
container_name: zot-2-1-22-registry
hostname: zot-registry
restart: unless-stopped
ports:
- "8150:5000"
volumes:
- ./config.json:/etc/zot/config.json:ro
- ./htpasswd:/etc/zot/htpasswd:ro
- /opt/data/quzuo/tmp/zot/data:/var/lib/registry
networks:
zot-net:
ipv4_address: 10.89.60.10
networks:
zot-net:
name: zot-2.1.22-net
driver: bridge
ipam:
config:
- subnet: 10.89.60.0/24
说明:
- zot 镜像入口命令固定为
serve /etc/zot/config.json(docker inspect实测:/usr/local/bin/zot-linux-amd64 [serve /etc/zot/config.json]),所以配置与口令文件必须挂到容器内这两个路径; - 两个配置文件以
:ro只读挂载,防止进程误写; - 数据挂载点
/var/lib/registry与镜像默认storage.rootDirectory一致; - 网络显式指定
10.89.60.0/24网段,避免与其他 Compose 项目默认网段冲突。
config.json(全文,相对镜像默认配置补上认证、把日志级别收窄并落盘):
{
"distSpecVersion": "1.1.0",
"storage": {
"rootDirectory": "/var/lib/registry",
"dedupe": true,
"gc": true,
"gcDelay": "1h",
"gcInterval": "24h"
},
"http": {
"address": "0.0.0.0",
"port": "5000",
"realm": "zot",
"auth": {
"htpasswd": {
"path": "/etc/zot/htpasswd"
},
"failDelay": 5
}
},
"log": {
"level": "info",
"output": "/var/lib/registry/zot.log"
},
"extensions": {
"search": {
"enable": true
},
"ui": {
"enable": true
}
}
}
各字段含义速览见第七章,完整参数族与生产模板见配套《Zot 2.1.22 配置参数详解》。
htpasswd(账号文件):zot 的 htpasswd 认证要求 bcrypt 散列($2b 前缀)。官方写法用 apache2-utils:htpasswd -bnB admin q1w2e3r4 > htpasswd;宿主机没有该工具时,实测可用 Python bcrypt 生成,效果相同:
$ python3 -c "import bcrypt; print('demo:' + bcrypt.hashpw(b'q1w2e3r4', bcrypt.gensalt(rounds=10, prefix=b'2b')).decode())"
demo:$2b$10$kmZ7BZolaoBpY7PjtaMSP.NGDr59OWAdL1b6HoNDkeunLkxL1xH7u
按同样方法生成 admin、reader 两行写入 htpasswd,本次实验实际使用的文件内容:
admin:$2b$10$h/AGifgcwXxamszKk.C/XOW77hiwyhbZosnnO0Ekdy1N3/C1tW0WK
reader:$2b$10$QzHAgTXxc8TjwAisCNDyc.GnO8sSBSqlw5g2w8ma7ro1OVyUGMV92
💡 技术拓展:htpasswd 文件格式是
用户名:口令散列,散列不是明文。Apache htpasswd 工具还支持 MD5($1)、SHA1({SHA})等算法,但 zot 只接受 bcrypt($2b),所以用 apache2-utils 时必须带-B参数,用 Python 时prefix=b'2b'。示例口令q1w2e3r4仅为实验明文演示,生产请换强口令并只保留散列。
4.2 启动服务
cd /opt/data/quzuo/tmp/zot/stack && docker compose up -d
实测回显:
Network zot-2.1.22-net Creating
Network zot-2.1.22-net Creating
Network zot-2.1.22-net Created
Network zot-2.1.22-net Created
Container zot-2-1-22-registry Creating
Container zot-2-1-22-registry Created
Container zot-2-1-22-registry Starting
Container zot-2-1-22-registry Started
4.3 确认运行状态与启动日志
docker compose ps
实测回显:
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
zot-2-1-22-registry ghcr.io/project-zot/zot:v2.1.22 "/usr/local/bin/zot-…" zot 3 seconds ago Up 2 seconds 0.0.0.0:8150->5000/tcp, [::]:8150->5000/tcp
本配置把 log.output 指向 /var/lib/registry/zot.log(即宿主机数据目录),日志不再走 stdout,所以 docker compose logs 是空的:
$ docker compose logs 2>&1 | head -8
启动过程看宿主机日志文件 /opt/data/quzuo/tmp/zot/data/zot.log,输出节选(实测,取启动关键四行):
{"time":"2026-11-01T07:47:45.950487247Z","level":"warn","message":"config dist-spec version differs from version actually used","config version":"1.1.0","supported version":"1.1.1","caller":"zotregistry.dev/zot/v2/pkg/cli/server/root.go:1300","func":"zotregistry.dev/zot/v2/pkg/cli/server.updateDistSpecVersion","goroutine":1}
{"time":"2026-11-01T07:47:45.953682196Z","level":"info","message":"loaded htpasswd file","htpasswd-file":"/etc/zot/htpasswd","users":2,"caller":"zotregistry.dev/zot/v2/pkg/api/htpasswd.go:70","func":"zotregistry.dev/zot/v2/pkg/api.(*HTPasswd).Reload","goroutine":1}
{"time":"2026-11-01T07:47:45.95487706Z","level":"info","message":"setting up ui routes","caller":"zotregistry.dev/zot/v2/pkg/extensions/extension_ui.go:72","func":"zotregistry.dev/zot/v2/pkg/extensions.SetupUIRoutes","goroutine":1}
{"time":"2026-11-01T07:47:45.954907633Z","level":"info","message":"finished setting up ui routes","caller":"zotregistry.dev/zot/v2/pkg/extensions/extension_ui.go:98","func":"zotregistry.dev/zot/v2/pkg/extensions.SetupUIRoutes","goroutine":1}
解读:首行 warn 是配置声明的 dist-spec 版本(1.1.0)低于二进制支持版本(1.1.1),zot 自动按 1.1.1 运行,无害(详见 FAQ 11.5);loaded htpasswd file 确认两个账号已加载;两行 ui routes 确认 Web 界面就绪。两行之间 zot 还会打印一整行 configuration settings 配置快照(单行 JSON,含全部生效参数),排障时可直接对照。
4.4 验证服务可达
$ curl -s -o /dev/null -w "%{http_code}" http://localhost:8150/v2/
401
$ curl -s -u admin:q1w2e3r4 http://localhost:8150/v2/ -w "\nHTTP %{http_code}\n"
HTTP 200
$ curl -s -u admin:q1w2e3r4 http://localhost:8150/v2/_catalog
{"repositories":["alpine"]}
$ curl -s http://localhost:8150/ | grep -o "<title>[^<]*</title>"
<title>zot OCI-native Container Image Registry</title>
$ curl -s -u admin:q1w2e3r4 http://localhost:8150/readyz -w " HTTP %{http_code}\n"
ok HTTP 200
解读:未带凭据访问 /v2/ 返回 401(Basic 质询,见 FAQ 11.4);带 admin 凭据返回 200;_catalog 此时已含 alpine(环境准备阶段先行推送的测试仓库,全新部署此处为空数组,第九章 8151 实例可对照);根路径返回 zot Web UI;/readyz 就绪探针返回 ok。
4.5 登录、推送与拉取闭环
$ echo q1w2e3r4 | docker login localhost:8150 -u admin --password-stdin
WARNING! Your credentials are stored unencrypted in '/root/.docker/config.json'.
Configure a credential helper to remove this warning. See
https://docs.docker.com/go/credential-store/
Login Succeeded
$ docker tag busybox:latest localhost:8150/demo/busybox:v1
$ docker push localhost:8150/demo/busybox:v1
The push refers to repository [localhost:8150/demo/busybox]
37bb94b0940b: Unavailable
37bb94b0940b: Pushed
v1: digest: sha256:f97baa533a26513c453a362be331a43eb60214302f449c16571b23cea141dce4 size: 610
Info -> Not all multiplatform-content is present and only the available single-platform image was pushed
sha256:fd7dc98638c8e305f4dc34e979f1c0fdfdcaeb0fbf8fcff77ae834b6da3d7e6e -> sha256:f97baa533a26513c453a362be331a43eb60214302f449c16571b23cea141dce4
$ curl -s -u admin:q1w2e3r4 http://localhost:8150/v2/_catalog
{"repositories":["alpine","demo/busybox"]}
删掉本地副本再拉回,验证仓库侧数据完整:
$ docker rmi localhost:8150/demo/busybox:v1 && docker pull localhost:8150/demo/busybox:v1
v1: Pulling from demo/busybox
Digest: sha256:f97baa533a26513c453a362be331a43eb60214302f449c16571b23cea141dce4
Status: Downloaded newer image for localhost:8150/demo/busybox:v1
localhost:8150/demo/busybox:v1
推拉 digest 一致(sha256:f97baa533a26513c453a362be331a43eb60214302f449c16571b23cea141dce4),闭环完成。Info -> 两行是 docker 提示本地仅有单平台架构镜像,属正常。
4.6 用 zli 客户端复核
zli 是 zot 官方命令行客户端(发布页下载 zli-linux-amd64)。先把 8150 实例登记为命名端点,再查状态:
$ ./zli-linux-amd64 config add zot8150 http://localhost:8150
$ ./zli-linux-amd64 config list
local (default) http://localhost:8080
zot8150 http://localhost:8150
$ ./zli-linux-amd64 status --config zot8150 -u admin:q1w2e3r4
Server Status: online
Server Version: v2.1.22
Dist Spec Version: 1.1.1
Built with: events, imagetrust, lint, metrics, mgmt, profile, scrub, search, sync, ui, userprefs
Built with 行直接列出该构建编译进去的全部扩展,与镜像选型(full 版)互相印证。zli 的搜索、CVE 报告、仓库管理等完整用法见配套《Zot 2.1.22 客户端操作与签名校验指南》。
五、数据持久化说明
全部仓库数据落在宿主机 /opt/data/quzuo/tmp/zot/data/(容器内 /var/lib/registry),zot 以标准 OCI image layout 存储,每个仓库一个目录:
$ ls -A /opt/data/quzuo/tmp/zot/data
alpine
cache.db
demo
meta.db
_sessions
zot.log
$ du -sh /opt/data/quzuo/tmp/zot/data
6.0M /opt/data/quzuo/tmp/zot/data
$ cd /opt/data/quzuo/tmp/zot/data && find . -maxdepth 3 | sort
.
./alpine
./alpine/blobs
./alpine/blobs/sha256
./alpine/index.json
./alpine/oci-layout
./alpine/.uploads
./cache.db
./demo
./demo/busybox
./demo/busybox/blobs
./demo/busybox/index.json
./demo/busybox/oci-layout
./demo/busybox/.uploads
./meta.db
./_sessions
./zot.log
| 路径 | 内容 | 说明 |
|---|---|---|
<仓库名>/blobs/sha256/ | 内容寻址 blob(层与 manifest) | 数据主体;dedupe 开启时相同层硬链接复用 |
<仓库名>/_manifests/、index.json、oci-layout | 标签索引与 OCI 布局标记 | 标准 OCI layout 结构 |
<仓库名>/.uploads/ | 分块上传暂存目录 | GC 会清理 |
cache.db、meta.db | zot 扩展元数据库(搜索/CVE 等) | 勿手工编辑 |
_sessions/ | Web UI 会话存储 | 勿手工编辑 |
zot.log | 运行日志(log.output 落点) | 排障入口 |
重启验证数据不丢:
$ cd /opt/data/quzuo/tmp/zot/stack && docker compose restart
Container zot-2-1-22-registry Restarting
Container zot-2-1-22-registry Started
$ curl -s -u admin:q1w2e3r4 http://localhost:8150/v2/_catalog
{"repositories":["alpine","demo/busybox"]}
⚠️ 注意:备份 = 打包数据目录 + stack 下两个配置文件;恢复时整目录放回、重新
up -d即可。因为落盘就是标准 OCI layout,理论上该目录也能被其他兼容实现直接服务。
六、环境变量说明
zot 镜像不读取任何运行时环境变量:编排文件没有 environment 段,全部行为由挂载的 config.json 驱动,容器入口命令固定 serve /etc/zot/config.json。这与 LinuxServer.io 系镜像靠 PUID/PGID/TZ 环境变量驱动部署的模型完全不同,对照如下:
| 需求 | zot 的做法 |
|---|---|
| 改端口、认证、扩展开关 | 编辑 config.json 对应字段后 docker compose restart |
| 改账号口令 | 重新生成 htpasswd 后 docker compose restart(见 FAQ 11.2) |
| 运行身份 | 镜像内以 root 运行,靠挂载目录属主控制数据权限 |
| 时区 | 无环境变量入口,日志时间戳为 UTC |
七、配置参数速览
本部署 config.json 各字段作用速记(完整参数族、默认值与生产模板见配套《Zot 2.1.22 配置参数详解》):
| 字段 | 本部署取值 | 作用 |
|---|---|---|
distSpecVersion | 1.1.0 | 声明兼容的 OCI Distribution Spec 版本,运行时自动按支持版本(1.1.1)执行 |
storage.rootDirectory | /var/lib/registry | 数据唯一落盘点(已映射宿主机目录) |
storage.dedupe | true | 相同内容层硬链接去重 |
storage.gc / gcDelay / gcInterval | true / 1h / 24h | 孤儿 blob 延迟回收、周期执行 |
http.address / port | 0.0.0.0 / 5000 | 容器内监听,宿主机映射 8150 |
http.realm | zot | Basic 质询的 realm 名 |
http.auth.htpasswd.path | /etc/zot/htpasswd | bcrypt 账号文件 |
http.auth.failDelay | 5 | 认证失败后延时 5 秒响应,抗口令爆破 |
log.level / output | info / /var/lib/registry/zot.log | 日志级别与落盘位置(导致 compose logs 为空) |
extensions.search.enable | true | GraphQL 搜索 + CVE 扫描扩展 |
extensions.ui.enable | true | Web 界面 |
八、配置文件挂载对照
| 宿主机路径 | 容器内路径 | 模式 | 内容 |
|---|---|---|---|
/opt/data/quzuo/tmp/zot/stack/config.json | /etc/zot/config.json | 只读 | 服务全部行为配置 |
/opt/data/quzuo/tmp/zot/stack/htpasswd | /etc/zot/htpasswd | 只读 | bcrypt 账号散列 |
/opt/data/quzuo/tmp/zot/data | /var/lib/registry | 读写 | OCI layout 仓库数据 + 扩展元数据 + 日志 |
九、等价 docker run 命令
不用 Compose 时,一条 docker run 即可拉起等价实例。以下为本环境实测(8151 端口、独立空数据目录,验证后已删除):
$ mkdir -p /opt/data/quzuo/tmp/zot/data-runtest && docker run -d --name zot-2-1-22-cli-test -p 8151:5000 -v /opt/data/quzuo/tmp/zot/stack/config.json:/etc/zot/config.json:ro -v /opt/data/quzuo/tmp/zot/stack/htpasswd:/etc/zot/htpasswd:ro -v /opt/data/quzuo/tmp/zot/data-runtest:/var/lib/registry ghcr.io/project-zot/zot:v2.1.22
3b77bd192723b28dc0b94c452cd63f992b6e040c685974bb43f68c325212de40
$ docker ps --filter name=zot-2-1-22-cli-test --format '{{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Ports}}'
3b77bd192723 zot-2-1-22-cli-test Up 8 seconds 0.0.0.0:8151->5000/tcp, [::]:8151->5000/tcp
$ curl -s http://localhost:8151/v2/ -o /dev/null -w "no-auth: HTTP %{http_code}\n"
no-auth: HTTP 401
$ curl -s -u admin:q1w2e3r4 http://localhost:8151/v2/ -w " HTTP %{http_code}\n"
HTTP 200
$ curl -s -u admin:q1w2e3r4 http://localhost:8151/v2/_catalog -w " HTTP %{http_code}\n"
{"repositories":[]} HTTP 200
全新空数据目录的 _catalog 为空数组,与 4.4 节对照。验证完清理:
$ docker rm -f zot-2-1-22-cli-test && rm -rf /opt/data/quzuo/tmp/zot/data-runtest
zot-2-1-22-cli-test
十、常用运维命令
| 操作 | 命令 |
|---|---|
| 启动 / 停止 | docker compose up -d / docker compose down |
| 重启(改配置或账号后) | docker compose restart |
| 查看状态 | docker compose ps |
| 看日志 | tail -f /opt/data/quzuo/tmp/zot/data/zot.log(compose logs 为空,见 FAQ 11.1) |
| 健康检查 | curl -s -u admin:q1w2e3r4 http://localhost:8150/readyz |
| 列仓库 | curl -s -u admin:q1w2e3r4 http://localhost:8150/v2/_catalog |
| 列标签 | curl -s -u admin:q1w2e3r4 http://localhost:8150/v2/demo/busybox/tags/list |
| 备份 | 停服后打包 /opt/data/quzuo/tmp/zot/data 与 stack 下 config.json、htpasswd |
docker compose down 实测回显(实验期间执行):
Container zot-2-1-22-registry Stopping
Container zot-2-1-22-registry Stopped
Container zot-2-1-22-registry Removing
Container zot-2-1-22-registry Removed
Network zot-2.1.22-net Removing
Network zot-2.1.22-net Removed
⚠️ 注意:zot 镜像是极简构建,容器内没有 shell 和任何工具,
docker exec zot-2-1-22-registry ls会报exec: "ls": executable file not found in $PATH。运维一律从宿主机用 curl/zli 进行,或读挂载出来的日志文件。
十一、FAQ
11.1 docker compose logs 没有任何输出?
本配置 log.output 指向 /var/lib/registry/zot.log,日志写文件、不再写 stdout,compose logs 自然为空。直接看宿主机 /opt/data/quzuo/tmp/zot/data/zot.log;若想恢复 stdout 采集(如接日志平台),删掉 log 段的 output 字段重启即可。
11.2 修改 htpasswd 后新账号不生效?
本部署把 htpasswd 以单文件 bind mount 映射进容器(4.1 节编排文件 volumes 段的 ./htpasswd:/etc/zot/htpasswd:ro),inotify 事件不透传,zot 的 htpasswd watcher 收不到变更,改完必须 docker compose restart。实测:把 4.1 节生成的 demo 行追加进 htpasswd 后直接登录返回 401——
$ curl -s -u demo:q1w2e3r4 http://localhost:8150/v2/ -w " new-user: HTTP %{http_code}\n"
{"code":"UNAUTHORIZED","message":"authentication required","detail":{"description":"The access controller was unable to authenticate the client. Often this will be accompanied by a WWW-Authenticate HTTP response header indicating how to authenticate."}} new-user: HTTP 401
重启后新账号 200、错误口令 401:
$ cd /opt/data/quzuo/tmp/zot/stack && docker compose restart
Container zot-2-1-22-registry Restarting
Container zot-2-1-22-registry Started
$ curl -s -u demo:q1w2e3r4 http://localhost:8150/v2/ -o /dev/null -w " new-user: HTTP %{http_code}\n"
new-user: HTTP 200
$ curl -s -u demo:wrongpass http://localhost:8150/v2/ -o /dev/null -w " wrong-pass: HTTP %{http_code}\n"
wrong-pass: HTTP 401
另注意镜像内没有 htpasswd 工具,docker exec zot-2-1-22-registry htpasswd -bnB admin q1w2e3r4 报:
OCI runtime exec failed: exec failed: unable to start container process: exec: "htpasswd": executable file not found in $PATH
生成散列一律在宿主机做(apache2-utils 或 4.1 节 Python 一行命令)。
另注意这是挂载形态的限制而非 zot 本身:裸机/目录级挂载下 zot 对 htpasswd 有独立 watcher,账号增删免重启热加载(实测日志 htpasswd file changed, trying to reload config,见《Zot 2.1.22 配置参数详解》 3.3 节)。想避开本坑可改为目录级挂载:把 config.json 与 htpasswd 放进同一目录,volumes 写成 - ./etc:/etc/zot:ro,watcher 即可收到变更事件。
11.3 宿主机 8150 端口被占用怎么办?
ss -ltn | grep 8150 确认后,把编排文件里 "8150:5000" 改成其他空闲端口(容器内 5000 不动),docker compose up -d 重建;docker login、curl、zli 等命令中的端口同步替换。
11.4 push / 验证前客户端先报 401,正常吗?
正常。OCI Distribution Spec 的认证流程就是:匿名请求 → 服务端 401 并带 WWW-Authenticate: Basic realm="zot" 质询 → 客户端带凭据重试。docker 会自动完成这一跳,只要 Login Succeeded、push 出现 Pushed 即可。
11.5 启动日志首行 warn「config dist-spec version differs」要紧吗?
不要紧。配置声明 1.1.0、二进制实际支持 1.1.1,zot 自动按更高版本运行(4.6 节 zli status 回显 Dist Spec Version: 1.1.1)。介意告警就把 distSpecVersion 改为 1.1.1。
11.6 restart 后 compose logs 出现 error「failed to start controller, exiting」?
zot-2-1-22-registry | {"time":"2026-11-01T08:13:56.708445987Z","level":"error","message":"failed to start controller, exiting","error":"http: Server closed","caller":"zotregistry.dev/zot/v2/pkg/cli/server/root.go:98","func":"zotregistry.dev/zot/v2/pkg/cli/server.newServeCmd.func1","goroutine":1}
这是旧进程收到停止信号、HTTP Server 被关闭时的收尾记录,属于重启噪音;随后新进程正常拉起(docker compose ps 状态 Up、/readyz 返回 ok 即可放心)。
十二、参考链接
- Zot 官方 Linux 安装指南:https://zotregistry.dev/v2.1.22/install-guide-linux
- Zot 管理配置参考:https://zotregistry.dev/v2.1.22/admin-guide/admin-configuration
- Zot 认证与授权(authn-authz):https://zotregistry.dev/v2.1.22/articles/authn-authz
- zli 用户指南:https://zotregistry.dev/v2.1.22/user-guides/zli
- 项目主页与发布:https://github.com/project-zot/zot、https://github.com/project-zot/zot/releases/tag/v2.1.22
- 配套篇:《Zot 介绍与选型指南》