Skip to content

OCIRepository fails to refresh public ghcr.io tags with 404 to github.com/-/v2/packages URL #2050

@rubasace

Description

@rubasace

Describe the bug

OCIRepository resources targeting public charts on ghcr.io fail to refresh their tag digest after the first successful pull. The reconciler ends up making a request to a non-public GitHub URL (github.com/-/v2/packages/container/package/...) that returns 404, leaving the resource stuck in Ready=False indefinitely.

The image is publicly accessible (verified via curl with an anonymous token exchange), and the digest matches what is pinned in the resource.

Steps to reproduce

  1. Apply the following on a cluster with source-controller v1.8.4 (Flux v2.8.7):

    apiVersion: source.toolkit.fluxcd.io/v1
    kind: OCIRepository
    metadata:
      name: dragonfly-operator
      namespace: flux-system
    spec:
      interval: 10m
      url: oci://ghcr.io/dragonflydb/dragonfly-operator/helm/dragonfly-operator
      timeout: 20m
      ref:
        tag: v1.5.0
  2. Wait for the next reconcile cycle (≥ 10m).

  3. Observe the controller logs and the resource status.

Actual behavior

OCIRepository reports Ready=False:

failed to determine artifact digest:
GET https://github.com/-/v2/packages/container/package/dragonflydb%2Fdragonfly-operator%2Fhelm%2Fdragonfly-operator%2Fmanifests%2Fv1.5.0:
unexpected status code 404 Not Found: Not Found

Note the URL is github.com/-/v2/packages/container/package/..., not the OCI distribution API endpoint ghcr.io/v2/.... This is an internal GitHub redirect target that is not a public OCI endpoint.

Reproducible with several public ghcr.io charts (e.g. dragonflydb/dragonfly-operator/helm/dragonfly-operator, weaveworks/charts/weave-gitops). Both produce the exact same error pattern.

Expected behavior

The reconciler should authenticate anonymously against ghcr.io (token endpoint ghcr.io/token with the right scope) and successfully retrieve the manifest, the same way docker pull / crane manifest / curl + Bearer do without credentials.

Evidence the image is reachable

Without auth (current source-controller behaviour):

curl -sI https://ghcr.io/v2/dragonflydb/dragonfly-operator/helm/dragonfly-operator/manifests/v1.5.0
# HTTP/2 401
# www-authenticate: Bearer realm="https://ghcr.io/token",
#                          service="ghcr.io",
#                          scope="repository:dragonflydb/dragonfly-operator/helm/dragonfly-operator:pull"

With anonymous token exchange:

TOKEN=$(curl -s 'https://ghcr.io/token?scope=repository:dragonflydb/dragonfly-operator/helm/dragonfly-operator:pull' \
  | jq -r .token)
curl -sI -H "Authorization: Bearer $TOKEN" \
        -H "Accept: application/vnd.oci.image.manifest.v1+json" \
        https://ghcr.io/v2/dragonflydb/dragonfly-operator/helm/dragonfly-operator/manifests/v1.5.0
# HTTP/2 200
# docker-content-digest: sha256:dd0656cdc1dc461e73336c88db5b664446621cdbcc65f0c44a184a4d34fe7c4c

So the registry path is correct and the manifest exists. The 401 → token → retry flow is what fails inside source-controller.

Workaround

Migrating the same chart to HelmRepository{type: oci} + HelmRelease.chart.spec works without any change in credentials or network setup, suggesting the code path used by HelmRepository{type: oci} handles the auth challenge correctly while OCIRepository does not.

Environment

  • Flux version: v2.8.7
  • source-controller: v1.8.4 (from gotk-components.yaml)
  • Install method: bootstrap via flux install
  • Kubernetes: v1.35 on Talos Linux v1.12.4
  • Registry: ghcr.io, public repos, no credentials configured

Related closed issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions