source-controller icon indicating copy to clipboard operation
source-controller copied to clipboard

OCIRepo URL unable to work with basePaths

Open karl-cardenas-coding opened this issue 2 months ago • 5 comments

I'm trying to use a Zot registry that I have configured with an ingress that points to a base path /zot.However, the ociRepo resource is unable to connect properly to the registry. During the v2 lookup it omits the basepath.

Here is my spec:

apiVersion: v1
items:
- apiVersion: source.toolkit.fluxcd.io/v1
  kind: OCIRepository
  metadata:
    creationTimestamp: "2025-10-14T05:45:58Z"
    finalizers:
    - finalizers.fluxcd.io
    generation: 1
    name: mural-spoke-definitions
    namespace: mural-system
    resourceVersion: "549568"
    uid: 6120aece-2a73-415f-9526-5e8a4335f542
  spec:
    insecure: true
    interval: 1m0s
    provider: generic
    ref:
      tag: mural-spoke-definitions
    secretRef:
      name: oci-basic-auth-hub
    timeout: 1m0s
    url: oci://example.abc.com/zot/mural-workloads # REPLACED WITH MOCK EXAMPLE

The status conditions are

  status:
    conditions:
    - lastTransitionTime: "2025-10-14T16:38:55Z"
      message: building artifact
      observedGeneration: 1
      reason: ProgressingWithRetry
      status: "True"
      type: Reconciling
    - lastTransitionTime: "2025-10-14T16:38:55Z"
      message: "failed to determine artifact digest: GET https://example.abc.com/v2/:
        unexpected status code 404 Not Found: <html>\r\n<head><title>404 Not Found</title></head>\r\n<body>\r\n<center><h1>404
        Not Found</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n;
        Get \"http://example.abc.com/v2/\": dial tcp 4.211.199.12:80:
        i/o timeout"
      observedGeneration: 1
      reason: OCIArtifactPullFailed
      status: "False"
      type: Ready
    - lastTransitionTime: "2025-10-14T16:38:55Z"
      message: "failed to determine artifact digest: GET https://example.abc.com/v2/:
        unexpected status code 404 Not Found: <html>\r\n<head><title>404 Not Found</title></head>\r\n<body>\r\n<center><h1>404
        Not Found</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n;
        Get \"http://example.abc.com/v2/\": dial tcp 4.211.199.12:80:
        i/o timeout"
      observedGeneration: 1
      reason: OCIArtifactPullFailed
      status: "True"
      type: FetchFailed
    observedGeneration: -1
kind: List
metadata:
  resourceVersion: ""

It's hitting the endpoint correctly but it needs to include the basepath for it to properly work. Below is an example with curl where I target the endpoint and get a response.

curl -u example:'notarealpwd' https://example.abc.com/zot/v2/_catalog
{"repositories":["zot/mural-workloads"]}

karl-cardenas-coding avatar Oct 14 '25 16:10 karl-cardenas-coding

hm... There is nothing in flux's OCIRepo spec that would let the registry client know that /zot/ is a "base path" where /v2/ should come after it rather than before it.

Is subPath hosting permitted by the OCI distribution spec? https://specs.opencontainers.org/distribution-spec/?v=v1.0.0 If this is allowed by the distribution spec, we're probably missing some basePath style definition and need to add one. I've never used an OCI client this way, but I can see why you might want this. (ex: hosting many services with a single DNS record)

Are pulls and metadata for artifacts and images working from other clients like skopeo, docker, or the kubelet? If Flux can't read your tags, other clients probably will also fail.

As a workaround for your use-case, try proxying /v2/ to zot as a passthrough with no path rewrite. Alternatively, rewrite /v2/* -> /zot/v2/*. It's possible the source-controller client will follow 301 redirects.

That might at least unblock you. Naturally, the tradeoff here is nothing else on that shared domain will be able to use the /v2/ prefix, but I believe this is an expectation of the spec.

stealthybox avatar Oct 14 '25 19:10 stealthybox

You're definitely not the first to try this. A prefix is supported when hosting the registry project as well. It seems primarily intended to be fronted by a reverse proxy that rewrites /v2/ on its own subdomain.

Related:

  • https://forums.docker.com/t/docker-push-does-not-support-url-prefix/137709
  • https://www.reddit.com/r/docker/comments/16vhhyu/private_docker_registry_behind_a_subpath/
  • https://github.com/GoogleContainerTools/jib/issues/2466

If we added support for this on the client-side in Flux, it would be a very non-standard feature.

stealthybox avatar Oct 14 '25 19:10 stealthybox

p.s. I don't know your full use-case, but if hosting multiple sub-domains is costly or impractical for you, you might take a look at using something like Tailscale/Headscale or Cloudflare tunnels. Both have free usage tiers that are great for homelabs and small businesses. They vastly simplify encrypted, dynamic, private networking.

stealthybox avatar Oct 14 '25 19:10 stealthybox

If we added support for this on the client-side in Flux, it would be a very non-standard feature.

I agree, it's up to the proxy to do the proper rewrite. In Flux we use a standard OCI client, same as crane ls or docker / oras, etc all of these will also fail.

stefanprodan avatar Oct 14 '25 20:10 stefanprodan

Thanks, @stealthybox!

We've confirmed that, as suggested, adding an additional Ingress resource to proxy zot under the domain's /v2 path resolves the issue:

# Original Ingress with redirect
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-production
    nginx.ingress.kubernetes.io/rewrite-target: /$1
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/use-regex: "true"
  name: zot-redirect
  namespace: mural-system
spec:
  ingressClassName: nginx
  rules:
  - host: example.abc.com
    http:
      paths:
      - backend:
          service:
            name: zot
            port:
              number: 5000
        path: /zot/(.*)
        pathType: ImplementationSpecific
---
# Additional Ingress to proxy /v2 on the base domain back to zot
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-production
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
  name: zot-proxy
  namespace: mural-system
spec:
  ingressClassName: nginx
  rules:
  - host: example.abc.com
    http:
      paths:
      - backend:
          service:
            name: zot
            port:
              number: 5000
        path: /v2
        pathType: Exact
      - backend:
          service:
            name: zot
            port:
              number: 5000
        path: /v2/
        pathType: Prefix

Definitely a large-ish caveat, but this should work for now!

TylerGillson avatar Oct 14 '25 22:10 TylerGillson