# AGENTS — distroless-mirror Public read-only OCI mirror of `gcr.io/distroless/{static,base,cc,python3}` on `img.dieschoenewolke.de`, served by zot on the VPS. `README.md` has the architecture, the deploy sequence and the zot-vs-registry:2 rationale. ## Rules - **Read-only is a property of the registry, not of the edge.** Keep `http.accessControl.repositories."**".anonymousPolicy` at `["read"]` and keep the config free of any `auth` block. Adding htpasswd or an LDAP block would create an authenticated write path that the internet could reach. - The port publish stays `127.0.0.1:5000`. The edge vhost is the only entry point; never publish zot on `0.0.0.0` or a host interface. - Keep the tag allowlist short (`latest|nonroot|debug|debug-nonroot`). It bounds both disk and what an anonymous client can make zot fetch from gcr.io. - `platforms` stays `linux/amd64` + `linux/arm64`. Without the allowlist zot copies every child of the index (arm, ppc64le, riscv64, s390x) and the mirror triples in size on a 28 GB root. - Marker tags (`synced---`) are written only after a successful pull **by digest** through the public hostname. Do not move tag creation before the verification, and do not mark from the digest comparison alone. - Sync one way only: VPS pulls from gcr.io; nothing pushes into the registry. ## Traps - `edge-nginx/nginx/default.conf` is a single-file bind mount. Write in place (`cat new > default.conf`), then `docker exec edge-nginx nginx -t` and `nginx -s reload` (or force-recreate). Replacing the file breaks the mount. - AppSec must stay off for this host: docker/containerd/skopeo/crane user agents trip CRS scanner rules. The CrowdSec IP bouncer still applies. - Do not test from the VPS: its own requests go through AppSec and can be challenged. Test from codeserver over the public name. - The wildcard `*.dieschoenewolke.de` SAN covers `img`; a new **name** needs a DNS record, not a certificate change. A new apex-level domain would need the certbot SAN list and a 300 s deSEC propagation wait. - zot's periodic sync updates an existing tag only on the next `pollInterval` (2 h). A digest mismatch in the auditor is therefore "pending", not "failed"; only a 404 for a ref that was already marked is a regression. - `proxy_buffering off` and `proxy_max_temp_file_size 0` are deliberate: layer blobs are streamed, not spooled onto the 28 GB root filesystem. - zot GC removes untagged manifests after `gcDelay` (1 h). Never rely on an untagged digest staying pullable — keep the tag (or a marker tag) that references it.