Repository navigation
roadmap: decide whether a registry layer (Harbor) earns its place in the supply chainΒ #2894
Description
π€ Generated by the Agentic Engineer
Maintainer direction (2026-08-02, interactive session): put a registry layer β specifically Harbor β
on the roadmap and evaluate it against our current supply chain.
Where we actually are
Our supply chain today is the triad this discussion describes, and it is already largely built:
| Layer | What we run |
|---|---|
| Storage | GHCR (ghcr.io), plus direct pulls from assorted upstream registries |
| Delivery / gating | Flux GitOps + Kyverno admission (168 references in-tree) |
| Signing | cosign first-party image verification (#2815, #2856) |
| Day-2 vulnerability | Kubescape operator (131 references; epic #2447 drives it to 100% and holds it) |
We run on Hetzner (Talos + Cilium + Flux + Longhorn + OpenBao) β not EKS/GKE/AKS, but also not an
air-gapped on-premise estate. That distinction matters, because it invalidates the usual framing in
both directions.
Evidence
1. Registry access control is a live, recurring pain β this is the strongest signal.
A cluster of open issues all circle the same limitation, that GitHub org/package permissions are too
coarse and too awkward for per-tenant image access:
- Scope tenant GHCR pull credentials to authorized packagesΒ #2889 (Security) β scope tenant GHCR pull credentials to authorized packages
- Single source of truth for the ghcr pull credential (OpenBao + ExternalSecret)Β #2613 (Epic) β single source of truth for the GHCR pull credential
- Skip the GHCR-auth reboot for nodes whose containerd already booted with the current credentialΒ #2638, Align Coroot API-key wiring and registry egressΒ #2685 β GHCR auth reboot handling and registry egress
We keep paying to work around a registry that was never designed for multi-tenant pull isolation.
2. Upstream registry availability is a measured CI cost.
On 2026-08-02 a docker/login-action step timed out reaching Docker Hub and failed a CI leg before
any test ran, while 20+ sibling matrix legs on the same run β several also logging into Docker Hub β
succeeded. It cost a full extra ~45-minute cycle. Docker Hub rate limiting is the same failure class.
Every third-party image we pull is an availability dependency we do not control.
3. Prior art exists and was never actually decided.
#785 ("Set up Harbor pull-through for all images") and #822 ("Configure Homelab to pull all images as
OCI through Harbor") were both closed NOT_PLANNED on 2025-10-18, minutes apart, with empty
bodies and no comments. That is a backlog sweep, not a reasoned rejection β so there is no recorded
decision to overturn, and equally no recorded rationale to inherit.
The honest case, separated from the generic pitch
The four arguments usually made for Harbor do not apply to us equally, and pretending otherwise
would set this work up to be justified by the wrong thing:
| Claim | Does it apply here? |
|---|---|
| Air-gapped operation | No. Not a requirement. The cluster has internet egress today and nothing asks it not to. |
| Data sovereignty / compliance | Weak. Container images are overwhelmingly public artifacts, not regulated data. Hetzner being EU-based is not a compliance driver we actually have. |
| Upstream supply-chain defense (proxy cache) | Strong. A pull-through cache decouples us from upstream availability and rate limits, and creates a single chokepoint where third-party base images can be scanned or blocked before anything builds on them. |
| Multi-tenant registry RBAC | Strong. Directly evidenced by #2889 and #2613 β this is the problem we are already paying for. |
The counterweight, stated plainly because it is the thing most likely to sink this. Harbor is a
stateful service β PostgreSQL, Redis, and object storage β on a single Longhorn-backed cluster, and
adopting it as the pull path makes it a hard dependency for every image pull in the estate. If
Harbor is down, nothing pulls, including the workloads needed to bring Harbor back. That is a
genuine availability regression traded for the benefits above, and this platform's recent pain has
been squarely operational/storage-related (see #2043). It also partly duplicates scanning we already
get from Kubescape, which is arguably better placed β it sees what is actually loaded at runtime.
So this epic is deliberately not "adopt Harbor". It is "decide whether a registry layer earns its
operational cost here, and if so adopt it in the narrowest form that captures the value".
Hypothesis
Introducing a registry layer in pull-through-cache-first form (not as the system of record for
first-party images) removes the tenant-RBAC workarounds and the upstream-availability coupling, at an
operational cost that is acceptable only if it can fail without taking image pulls down with it.
Success signals
- Tenant image access expressible per-project without hand-scoped GHCR credentials (closes the class
behind Scope tenant GHCR pull credentials to authorized packagesΒ #2889 / Single source of truth for the ghcr pull credential (OpenBao + ExternalSecret)Β #2613). - Upstream-registry-caused CI failures and rate-limit incidents trend to zero.
- Guardrail: no new single point of failure in the pull path β a registry outage must degrade to
upstream pulls, not to a stalled cluster. If that cannot be shown, the answer is no.
Smallest useful first step
A timeboxed Spike that decides this on evidence rather than on a feature table, since the case is
genuinely mixed and the generic recommendation for a non-air-gapped cloud environment is not to add
Harbor. Deployment work is only unblocked if the spike says yes.
Acceptance criteria
- Spike completed with a recorded decision (adopt / adopt-narrowly / decline), including the
failure-mode analysis for the pull path. - If declining: the rationale is written down here and Set up Harbor pull-through for all imagesΒ #785/Configure Homelab to pull all images as OCI through HarborΒ #822 are referenced, so this is not
re-litigated a third time from scratch. - If adopting: decomposed into independently shippable children (deploy + storage, pull-through
caching, tenant RBAC migration, Kyverno/cosign interaction, backup/restore + failure drill). - Interaction with Kubescape (roadmap: Kubescape security stack β 100% and hold (posture Β· CVE Β· runtime)Β #2447) and cosign verification (security: tighten cosign subject matchers at the image-verification layerΒ #2815, First-party image pull verification does not block an unsigned first-party imageΒ #2856) is explicit β no
duplicated scanning gates, no weakened signature verification.
Part of #2043
Generated by Claude Code
Metadata
Metadata
Assignees
Labels
Type
Fields
Projects
- StatusShow more project fieldsβ Done
Activity
coderabbitai commentedon Aug 2, 2026
π Related PRs
#2625 - fix(deploy): synchronize production GHCR pull auth [merged]
#2633 - fix(deploy): verify incoming image on Talos nodes [merged]
#2635 - fix(deploy): reboot stale nodes so containerd adopts refreshed GHCR auth [merged]
#2793 - feat(cilium): activate homogeneous device selection [merged]
#2830 - feat(ci): publish Kubescape posture findings to Code Scanning [merged]
π Issue Planner
Check the box below or use the
@coderabbitai plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.π§ͺ Issue enrichment is currently in open beta.
You can configure auto-planning by selecting labels in the issue_enrichment configuration.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:π¬ Have feedback or questions? Drop into our discord!
devantler commentedon Sep 23, 2026
Decision: decline. No registry layer, neither Harbor nor a pull-through cache, for now.
This records the outcome of the #2895 spike. The evidence is from
mainat140db93and CI history for 2026-09-09 β 2026-09-22.1. The access-control problem is real, but it is not about the registry
Today there is no registry, mirror or cache: every node pulls straight from upstream. The pain comes from spreading one GHCR credential to many consumers:
talos/cluster/authenticate-ghcr-pulls.yaml)About 6,500 lines of bridge scripts keep these in step. Rotating the credential reboots nodes, because containerd reads registry auth only at start.
A project-scoped registry would not cleanly fix the open issues:
2. Registries do not break things today
ghcr.io/registry-1.docker.iothat "succeed on kubelet retry" (the Coroot SLO note ink8s/providers/hetzner/infrastructure/coroot/patches/enable-ha.yaml).3. Availability and cold start β the deciding point
A transparent mirror is safe. Talos falls back to the upstream registry when every mirror endpoint fails, unless
skipFallbackis set (Talos RegistryMirrorConfig). A cache that is down or not yet started therefore degrades to today's behaviour.Harbor as the credential holder is not safe. Removing the GHCR credential from nodes means private pulls can no longer fall back to upstream. Three things would then depend on Harbor being up:
Harbor in turn needs CloudNativePG, Redis, storage and its own pods running first. Keeping the node credential as a fallback avoids that loop, but then the rotation reboots and the credential spread from point 1 remain.
There is no credible cold-start answer that also delivers the benefit, so by the spike's own rule this is a decline.
4. It would overlap with checks we already have
verify-app-images(Deny) at admissionImageVerificationConfigat pullghcr.io/devantler-tech/match rules. (A transparent Talos mirror keeps the names, so this does not apply to it.)5. Operating cost
To run Harbor with high availability, we must supply highly available PostgreSQL, Redis and storage (Harbor HA).
6. Cheaper alternatives, on the same terms
distributionregistry or zot, behind a Talos mirror with fallback) would absorb Docker Hub limits. The measured Docker Hub failure count is zero.Revisit when
This also gives #785 and #822 their missing reasoning; both are cross-referenced to this comment.
Generated by Claude Code