自架 Docker Hub 替代品這件事,繞來繞去只剩兩個選擇。要嘛跑一支 registry:2,也就是官方的 Distribution,換來的是連刪一個 tag 都得手動觸發 blob 掃描的 1990 年代體驗;要嘛把 Harbor 整套 8 個 container、外加 PostgreSQL 跟 Redis 開下去,光基礎架構就吃掉一台 4 GB 的 VPS。Zot 這個 CNCF 專案存在的意義就是把中間層補起來——一支 Go binary,功能該有的都有,資源佔用像 Distribution,功能像 Harbor。
Zot 於 2022 年進入 CNCF sandbox,2026 年目前穩定版本落在 2.1.x 系列。它跟其他 registry 最大的差異不在單一功能,而在它從第一天就決定不弄自家格式:磁碟上直接就是 OCI Image Layout,Distribution v2 API 是唯一的對外介面,沒有自訂 metadata 也沒有專屬資料庫。想搬去別的 registry,用 oras cp 或直接複製目錄就行。
Distribution 撐不起私有 registry 的三件事
registry:2 這支 image 到今天累積下載量早就破十億,但它本質上只是「把 blob 存到磁碟」這件事包成 HTTP。刪 tag 之後底下的 blob 不會被回收,得停機跑一次 registry garbage-collect;沒有內建 UI,想看倉庫有哪些 image 只能敲 curl /v2/_catalog;沒有簽章驗證、沒有 vulnerability scan、沒有多站同步。
這些缺項在只有一個開發者、一台伺服器的場景下無所謂。但當團隊變成三五個人、映像倉庫超過 200 個之後,每個月要處理的事情——「這個 image 有沒有被誰簽過」、「上禮拜推的那版哪個 CVE 沒修」、「另一機房的 mirror 有沒有同步到最新版」——Distribution 完全不管。
而傳統選擇 Harbor 補上這些洞的方式是把每個功能都拆成獨立服務。Harbor 2.11 的預設 docker-compose 會拉起 nginx、core、jobservice、portal、registry、registryctl、trivy-adapter、log、redis、postgresql 十個 container;正式環境還要再加 Notary、Chartmuseum、Exporter。就算沒開高可用,光把它跑起來 RAM 就要 3 GB 起跳,設定檔一改就得整套重啟。
一個 binary 塞得下多少東西
Zot 的組成剛好相反:核心 registry、Web UI、search、cosign/notation 驗章、Trivy 掃描、Prometheus metrics、跨 registry 同步——全部編進同一支 binary,透過 build tag 決定要不要包進去。官方預先構建的 zot-linux-amd64-minimal 只有 20 MB 出頭,全功能版本的 zot-linux-amd64 也不到 100 MB。
啟動只需要一個 JSON config 跟一個資料目錄。最小可用設定長這樣:
1 | { |
跑起來就是一台完整的 OCI 1.1 registry,docker push 立刻可用。要開 UI、搜尋、掃描,就在 extensions 區塊裡追加設定,不用另外拉服務。UI 走的是 React 靜態檔案,直接由 registry 自己 serve,不需要另外一個 nginx。
線上垃圾回收跟去重是實際會用到的功能
Distribution 的垃圾回收必須離線執行,官方文件寫得很清楚:跑之前要先把 registry 設成 read-only 或直接關掉,不然會誤刪正在上傳的 blob。對只在半夜有窗口的營運環境已經很痛,對 CI 一天推幾百次的團隊根本沒辦法用。
Zot 內建的 GC 是線上執行的,透過 dedupe 與 retentionPolicy 兩層機制運作。前者用 hard link 把不同倉庫、同一 digest 的 blob 指到同一份實體檔案——實測在有 5 個以上服務共用同一組 base image 的環境下,磁碟用量會少掉 30% 到 50%。後者則按規則保留最近 N 個 tag、N 天內的 tag,或匹配特定 pattern 的 tag,過期的直接淘汰,不需要停機。
值得一提的細節:Zot 的 dedupe 判斷是靠 SHA-256 digest,不是靠檔名,所以即使兩個團隊命名習慣不同、把同一份 image tag 成不同名字,底下的 layer 還是共用同一份實體。這件事在 monorepo 或多環境 CI 場景會很快回本。
Cosign 驗章跟 Trivy 掃描不用另外裝
供應鏈安全這幾年被講到爛,但實際自架 registry 想開啟簽章驗證,Harbor 要另外裝 Notary 或設定 Cosigned webhook;Distribution 則是完全沒得選。Zot 把 cosign 跟 notation 兩套驗章協定都直接編進 binary,在 config 打開 imagetrust extension 就啟用了:
1 | "extensions": { |
一開啟後所有 image 都會顯示簽章狀態,未簽或簽章不符的 image 可以透過 accessControl 直接拒絕拉取。CVE 掃描則是背景執行 Trivy,把結果快取在本地 Bolt 資料庫,UI 上直接標示每個 tag 有幾個 High/Critical。整個過程不需要另外一個服務,也不需要外接資料庫。
多機房同步比想像中省事
跨機房或多環境部署常見的需求是「開發環境的 image 推到 A 站台之後,正式環境的 B 站台自動同步過去」。Distribution 沒有這個功能,Harbor 靠 replication 規則做到但要在 UI 一條一條設。
Zot 的 sync extension 走的是宣告式設定,把來源 URL、輪詢間隔、想同步的倉庫 pattern 寫進 config 就好:
1 | "sync": { |
onDemand 打開之後,B 站台收到不存在的 image 拉取請求時會即時去 A 站台抓,抓完存在本地並回應。這個模式特別適合邊緣節點——不需要事先預熱所有 image,用到才拉,拉過就快取。
儲存後端可以直接接物件儲存
正式環境不會想把 blob 全部存在單台伺服器的本地磁碟上。Zot 透過 Rclone-like 的抽象層支援 filesystem、S3、Azure Blob 三種後端,設定改一個欄位就能切換:
1 | "storage": { |
搭配前面提到的 dedupe 與 GC,這一組設定就能撐起橫向擴展——多台 Zot 節點共用同一個 S3 bucket,透過 DynamoDB 或前置的 Redis 作為 dedupe 快取,前面掛 L4 load balancer 分流。對比 Harbor 要處理 PostgreSQL 主從、Redis Sentinel、shared storage NFS,運維成本天差地遠。
什麼情況不該用 Zot
Zot 不是萬用解。三個場景要考慮清楚:需要 Helm chart repository、需要多租戶 UI 級別的 project 隔離、需要 replication 有可視化的 job history——這三件事 Harbor 做得比較完整。Zot 的 UI 定位是「dev-friendly 的檢視器」,不是給 admin 做細部配額管控的工作台。
另外要注意 Zot 的效能定位。單節點在 SSD 上大約可以撐到每秒幾百次的 manifest 存取,超過這個規模就要考慮多節點加共用物件儲存。對絕大多數自架團隊而言這條上限離得很遠,但如果流量已經在 CDN 級,Zot 未必比得過商用方案。
該不該從 Distribution 或 Harbor 遷過來
從 Distribution 遷過來幾乎沒有痛點。Distribution 存的就是 OCI Image Layout,Zot 讀的也是 OCI Image Layout,把資料目錄整個複製過去、掛上 Zot 就繼續跑,唯一要重新設定的是認證方式跟 UI 想開哪些 extension。實測搬 200 個倉庫、大約 40 GB 的 blob,rsync 完之後 Zot 大概花 30 秒掃描一次目錄,UI 就把所有 image 都列出來了。
從 Harbor 遷則要用 skopeo copy 或 oras cp 逐個倉庫搬,因為 Harbor 的 project 概念在 Zot 沒有對應。多花點時間但值得——之後不用再維護一整包 docker-compose、不用擔心 PostgreSQL 升級搞壞 metadata、config 改完 SIGHUP 一下就生效,而不是整套重啟。
私有 registry 是自架服務裡最容易被忽略的一環。一台跑得穩、能掃 CVE、能驗簽章、能跨機房同步的 registry,對整條 CI/CD pipeline 的可靠度影響遠比選哪個 orchestrator 大。
需要在臺灣本地部署低延遲的私有 registry,NCSE Network 提供高效能 NVMe SSD 的 VPS 主機與跨機房 IP Transit 服務,可以直接把 Zot 架起來,並串接南港與其他地點做多站同步。歡迎至 ncse.tw 了解 VPS 主機方案。