OCIRepo URL unable to work with basePaths
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"]}
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.
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.
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.
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.
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!