zot version: v2.1.20 (-events-imagetrust-lint-metrics-mgmt-profile-scrub-search-sync-ui-userprefs binary), filesystem storage on an NFS mount (Synology, nfsvers=4.1, hard), dedupe+gc on, sync extension with 8 onDemand registries.
What happened (production, 2026-08-25): an in-flight on-demand sync of a ~10 GB multi-arch image (docker.io/gpustack/runner) was interrupted (client connection cut at our HTTP writeTimeout, then the pod was restarted while a second demand for the same image was in flight). After restart, a stale staging tree remained at <repo>/.sync/<uuid>/ (~18.5 GiB). From then on:
- Every by-tag manifest GET on EVERY repository hung indefinitely (each triggers an upstream freshness sync that appeared to queue forever — even for tiny, already-cached images on unrelated repos/registries).
- By-digest manifest GETs and
/v2/_catalog remained fast (local store healthy).
- The process was near-idle (few mCPU), no
Copy layer / sync progress log lines, no errors logged; readiness/liveness stayed green.
- Upstream (Docker Hub) and storage were verified healthy from the same host at the same time.
Recovery: stop zot, delete all .sync staging dirs, start zot → by-tag GETs immediately behaved normally (~2 s freshness syncs) and re-warms of the same big image completed fine (with a plain post-download commit/verify phase of up to ~12 min for ~10 GB on NFS, which is expected and distinct from the hang).
Suspicion: the on-demand sync worker pool / per-image demand channel can be exhausted or deadlocked when stale staging from a killed sync is present (possibly interacting with image already demanded, waiting on channel bookkeeping across restart), starving every subsequent freshness sync. A startup sweep of orphaned .sync dirs (or ignoring them for demand accounting) would likely prevent the state entirely.
Config highlights: onDemand: true x8 registries, maxRetries: 3, retryDelay: 1m, maxRetryDelay: 10m, SyncTimeout default 3h, storage gc 1h. Happy to provide fuller logs/config on request.
zot version: v2.1.20 (
-events-imagetrust-lint-metrics-mgmt-profile-scrub-search-sync-ui-userprefsbinary), filesystem storage on an NFS mount (Synology, nfsvers=4.1, hard), dedupe+gc on, sync extension with 8 onDemand registries.What happened (production, 2026-08-25): an in-flight on-demand sync of a ~10 GB multi-arch image (docker.io/gpustack/runner) was interrupted (client connection cut at our HTTP writeTimeout, then the pod was restarted while a second demand for the same image was in flight). After restart, a stale staging tree remained at
<repo>/.sync/<uuid>/(~18.5 GiB). From then on:/v2/_catalogremained fast (local store healthy).Copy layer/ sync progress log lines, no errors logged; readiness/liveness stayed green.Recovery: stop zot, delete all
.syncstaging dirs, start zot → by-tag GETs immediately behaved normally (~2 s freshness syncs) and re-warms of the same big image completed fine (with a plain post-download commit/verify phase of up to ~12 min for ~10 GB on NFS, which is expected and distinct from the hang).Suspicion: the on-demand sync worker pool / per-image demand channel can be exhausted or deadlocked when stale staging from a killed sync is present (possibly interacting with
image already demanded, waiting on channelbookkeeping across restart), starving every subsequent freshness sync. A startup sweep of orphaned.syncdirs (or ignoring them for demand accounting) would likely prevent the state entirely.Config highlights:
onDemand: truex8 registries,maxRetries: 3, retryDelay: 1m, maxRetryDelay: 10m,SyncTimeoutdefault 3h, storage gc 1h. Happy to provide fuller logs/config on request.