The Honest Ceiling: Why 38 of 94 Images Can Never Route Through Harbor
Maximum Harbor routing is 56 of 94 images — the other 38 run only in namespaces that must keep pulling direct, each one a dependency loop with a written reason. The honest framing: routing raises patchability, not visibility.
The registry-routing campaign has a number attached, and it is not 94. The maximum reachable through Harbor is 56 of 94 images — 7 already there, 12 from the docker.io phase, 37 across the nine registries. The other 38 cannot route, ever, and that is not an open gap. Every one of them runs only in a namespace that must keep pulling direct, and the list of reasons is written down.
The math in one bar
8 in Harbor today · +12 phase 1 (docker.io, active) · +37 phase 2 (nine registries, staged) = 56 of 94 (60%). 38 remain outside, permanently.
One subtlety makes the number smaller rather than larger: rules are namespace-scoped, so an image that runs in both a safe and an excluded namespace still gets cached and scanned via the safe one. 38 is what remains after that deduplication — each is a namespace whose every image is excluded.
The split, and the honest framing underneath: routing buys scan-on-push, SBOMs and Copacetic reach (8 → 56 images) — not visibility. The 38 are still fully scanned at runtime by Trivy Operator. There was never a visibility gap.
Exclusions are not laziness
Each of the 38 would create a dependency loop: Harbor needs the thing, and the thing needs Harbor. Cilium is the CNI Harbor needs in order to serve any traffic at all. cert-manager issues Harbor's TLS certificate. external-secrets supplies its credentials. flux-system delivers the policy that defines the routing itself. The next post walks the full list, loop by loop.
The deeper point is the framing. A coverage dashboard that reads "56%" invites the question "why not 100%?" — and the answer is architecture, not effort: a registry that everything must pull through is also a registry whose outage prevents its own repair.
Where the 94 comes from
The denominator is itself a measurement with a method: distinct images running in the cluster, deduplicated by digest, counted from Pods rather than from workload templates (the registry-label problem again — counting from templates gave different numbers at different phases of the routing work). The 94 is the stable, digest-level count. Any coverage claim against it inherits the method: say "56 of 94" and you have also said "counted from running pods, deduplicated by digest."
The 38, categorized
The exclusions sort into two families:
Not ours to operate — 21 images, all kube-system. Scaleway manages the control plane; routing it would mean taking responsibility for images we cannot roll back, including Cilium itself — the CNI Harbor needs in order to serve any traffic at all. The list is concrete: cilium agents and operators, coredns, kube-proxy, metrics-server, the CSI drivers and their registrars.
Dependency loops — 17 images. harbor (10), kyverno (6), flux-system (4), envoy-gateway-system (2), cert-manager (3), external-secrets (1), dragonfly-operator-system (2) — each namespace is a component that Harbor needs in order to start, serve, or be reconfigured. The next post walks them loop by loop.
The two families have different textures. The kube-system set is a governance boundary — the provider's control plane is theirs. The loop set is a recovery boundary — these components participate in Harbor's own repair path. Both produce the same rule (exclude), but only the second generalizes: any future platform component that joins Harbor's repair path joins the exclusion list, and the list documents the criterion so the next addition is a classification, not a debate.
The dashboard consequence
A 56% ceiling changes how the routing dashboard should be read. Coverage panels that display "images through Harbor / total images" will asymptote near 60%, and a team that expects 100% will read a successful campaign as a stalled one. The fix the docs chose is framing, not fudging: the dashboard's routing section shows the ceiling alongside the progress, and the honest sentence attached to it is worth quoting in full — routing raises Copacetic's reach from 8 images to 56. It does not change what we can see. Every unroutable image is still fully scanned at runtime; the visibility number was always 94 of 94.
Next: the dependency loops — every exclusion, with its failure mode.