Skip to content

roadmap: decide whether a registry layer (Harbor) earns its place in the supply chainΒ #2894

Closed
1 of 1 issue completed

Description

@devantler

πŸ€– 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:

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

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

Part of #2043


Generated by Claude Code

Activity

  1. added theissue type on Aug 2, 2026
  2. coderabbitai commented on Aug 2, 2026

    @coderabbitai
    Contributor
    πŸ”— 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 plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    πŸ§ͺ 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:

    issue_enrichment:
      auto_enrich:
        enabled: false

    πŸ’¬ Have feedback or questions? Drop into our discord!

  3. moved this to πŸ“₯ Backlog in 🌊 Project Boardon Aug 2, 2026
  4. devantler commented on Sep 23, 2026

    @devantler
    ContributorAuthor

    πŸ€– Generated by the Agentic Engineer (interactive Claude Code session)

    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 main at 140db93 and 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 node auth (talos/cluster/authenticate-ghcr-pulls.yaml)
    • the Flux root artifact
    • OpenBao
    • Kyverno signature reads
    • every tenant namespace

    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

    • CI: 6,471 runs in the window; 123 failed.
      • Reading the logs of an evenly spread sample of 60 failed jobs found exactly one registry error: a GHCR 404 caused by our own stale tag reference, fixed by fix(security): the signature inventory misreads an image referenced by both tag and digestΒ #4038 (the next scheduled run passed).
      • The error lines of all 249 failed jobs contain no other registry error.
      • There was no Docker Hub rate-limit, timeout or 5xx at all.
      • The real failure causes are our own gates, for example the production-authorization check (52 of 141 root-cause jobs).
    • Cluster: the only record is transient containerd connection resets to ghcr.io / registry-1.docker.io that "succeed on kubelet retry" (the Coroot SLO note in k8s/providers/hetzner/infrastructure/coroot/patches/enable-ha.yaml).
    • A pull-through cache would be justified by a measured failure rate. That rate is currently zero.

    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 skipFallback is 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:

      • the ksail-operator image, which relies on node auth
      • the Talos first-party signature check, which reads signatures from GHCR with the node credential
      • every tenant's private image

      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

    • Scanning: Kubescape's vulnerability scanner already builds SBOMs and scans images from every registry. Harbor's Trivy would repeat that work.
    • Signatures: these are already enforced twice:
      • Kyverno verify-app-images (Deny) at admission
      • Talos ImageVerificationConfig at pull
    • Harbor's "prevent unsigned" would be a third gate that could disagree with the other two.
    • Any setup that rewrote image names would also bypass the ghcr.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).

    • Reused from what the platform already runs: CloudNativePG, R2 and Longhorn.
    • New: a shared Redis (today only fleetdm bundles one) and at least two replicas of each Harbor component.
    • Consequence: all of this would sit in the pull path of every workload, on a platform whose recent failures come from its own operations, not from upstream.

    6. Cheaper alternatives, on the same terms

    • Spegel is a stateless peer-to-peer mirror of images already on nodes. It falls back to upstream when no peer has the image, needs no database, and has a Talos guide (Spegel, Talos guide).
      • Would help: re-pulls after node rolls and scale-outs, which the image garbage-collection thresholds (75/70%) make more frequent.
      • Would not help: a cold pull or access control.
      • Caveat: it serves cached private images to anything that can reach its port (Spegel FAQ), so that port would need locking down first.
    • A plain pull-through cache (the distribution registry or zot, behind a Talos mirror with fallback) would absorb Docker Hub limits. The measured Docker Hub failure count is zero.
    • Both are sound designs, but neither has a problem to solve today.

    Revisit when

    • Registry-caused failures become a measurable rate: three or more in a 14-day window, or any Docker Hub rate limit in CI or cluster events. Try Spegel or a fallback-safe cache first.
    • Image pulls are shown to slow node rolls or scale-outs. Try Spegel first, with its port locked down.
    • A real air-gap or data-sovereignty requirement appears.

    This also gives #785 and #822 their missing reasoning; both are cross-referenced to this comment.


    Generated by Claude Code

  5. moved this from πŸ“₯ Backlog to βœ… Done in 🌊 Project Boardon Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Fields

    No fields configured for Epic.

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions