design/20260120.images-in-the-helm-chart.md
imageRegistry and imageNamespaceThis checklist contains actions which must be completed before a PR implementing this design can be merged.
The cert-manager Helm chart is painful to use with OCI mirroring registries (e.g. Artifactory), as shown by multiple issues and PRs:
In many Helm charts, users can switch registries by setting a single value.
With cert-manager today, users need to rewrite the full image reference for every component image. This proposal introduces two global values (imageRegistry, imageNamespace) so that users can switch registries and/or namespaces once.
The current values structure makes it too easy for defaults and overrides to conflict, and it encourages users to copy/paste full image references into multiple places. This is error-prone and makes mirroring workflows unnecessarily hard.
imageRegistry, imageNamespace) so that users can switch registries and/or namespaces once.--set webhook.image.repository=ghcr.io/my-cert-manager/my-webhook (or similar repository overrides).Today, cert-manager charts treat image.repository as “the full repository path” and (optionally) image.registry as an extra prefix.
Current defaults look like:
# Current defaults:
image:
# registry: quay.io
repository: quay.io/jetstack/cert-manager-controller
And in pseudocode:
if values.image.registry != "" {
repository = values.image.registry + "/" + values.image.repository
} else {
repository = values.image.repository
}
if values.image.digest != "" {
tagAndDigest = "@" + values.image.digest
} else {
tagAndDigest = ":" + values.image.tag
}
fullImage = repository + tagAndDigest
This makes registry mirroring tedious because users must override repository (and sometimes registry) for each component image.
Introduce two additional Helm values, they will look like:
# The container registry
# +docs:property
imageRegistry: quay.io
# The repository namespace
# Examples:
# - jetstack
# - cert-manager
imageNamespace: jetstack
This design relies on per-component image.name values which can be overridden by users. The chart uses these names when constructing the image repository from imageRegistry and imageNamespace.
Examples of where these fields would live:
image.name (controller)webhook.image.namecainjector.image.namestartupapicheck.image.nameacmesolver.image.nameImage repository construction for a given component becomes:
if values.image.repository != "" {
repository = values.image.repository
} else {
repository = values.image.name
if values.imageNamespace != "" {
repository = values.imageNamespace + "/" + repository
}
if values.imageRegistry != "" {
repository = values.imageRegistry + "/" + repository
}
}
fullImage = repository + tagAndDigest
This story covers the main user-facing benefit of the proposal: users can switch image locations by setting a small number of global values, instead of rewriting full image references per component.
Users of the Helm chart with Artifactory-mirrored images have to repeat the same thing over and over:
image:
registry: artifactory.domain.com/docker
repository: jetstack/cert-manager-controller
acmesolver:
image:
registry: artifactory.domain.com/docker
repository: jetstack/cert-manager-acmesolver
webhook:
image:
registry: artifactory.domain.com/docker
repository: jetstack/cert-manager-webhook
cainjector:
image:
registry: artifactory.domain.com/docker
repository: jetstack/cert-manager-cainjector
startupapicheck:
image:
registry: artifactory.domain.com/docker
repository: jetstack/cert-manager-startupapicheck
With this proposal, they can set a single value:
imageRegistry: artifactory.domain.com/docker
Resulting repository:
artifactory.domain.com/docker/jetstack/cert-manager-startupapicheck
<---------------------------> <------> <-------------------------->
imageRegistry imageNamespace image.name
imageRegistry: private-registry.venafi.cloud
imageNamespace: cert-manager
private-registry.venafi.cloud/cert-manager/cert-manager-startupapicheck
<---------------------------> <----------> <-------------------------->
imageRegistry imageNamespace image.name
If FIPS images are selected by using -fips in the image name, users still only need to configure the global registry/namespace once.
imageRegistry: private-registry.venafi.cloud
imageNamespace: cert-manager
image:
name: cert-manager-controller-fips
acmesolver:
image:
name: cert-manager-acmesolver-fips
webhook:
image:
name: cert-manager-webhook-fips
cainjector:
image:
name: cert-manager-cainjector-fips
startupapicheck:
image:
name: cert-manager-startupapicheck-fips
private-registry.venafi.cloud/cert-manager/cert-manager-startupapicheck-fips
<---------------------------> <----------> <-------------------------->
imageRegistry imageNamespace image.name
Here is a comparison of how other cert-manager-related projects structure their Helm values for images:
| Project | Where are images configured? | Existing default for image.registry | Existing image.repository | Notable differences |
|---|---|---|---|---|
| csi-driver | image.* + per-component images | no default set | quay.io/jetstack/cert-manager-csi-driver | Per-component sidecars (node registrar, liveness) with their own image blocks |
| istio-csr | image.* | no default set | quay.io/jetstack/cert-manager-istio-csr | Standard single image block |
| google-cas-issuer | image.* | "" | quay.io/jetstack/cert-manager-google-cas-issuer | Registry field exists but default is empty |
| aws-privateca-issuer | image.* | field doesn't exist | public.ecr.aws/k1n1h4h4/cert-manager-aws-privateca-issuer | No registry override field at all |
| approver-policy | image.* | no default set | quay.io/jetstack/cert-manager-approver-policy | Standard single image block |
| trust-manager | image.* + defaultPackageImage.* | no default set | quay.io/jetstack/trust-manager | Has global.rbac only; no global image settings |
| csi-driver-spiffe | image.* with repo map (driver,approver) | no default set | See (1) | image.repository is a map, not a string |
| openshift-routes | image.* | no default set | ghcr.io/cert-manager/cert-manager-openshift-routes | Standard single image block |
| cert-manager (proposal) | image.* + per-component images | no default set | quay.io/jetstack/cert-manager-csi-driver-spiffe | each component has its own <component>.image.* block, controller uses image.* |
(1) For csi-driver-spiffe, the image.repository is the only one that uses a map
with keys driver and approver, each containing full image names, i.e.,
image:
repository:
driver: quay.io/jetstack/cert-manager-csi-driver-spiffe
approver: quay.io/jetstack/cert-manager-csi-driver-spiffe-approver
image.registry.
image.registry is still accepted but warned against.