Back to Cert Manager

Image Configuration in Helm Chart: add `imageRegistry` and `imageNamespace`

design/20260120.images-in-the-helm-chart.md

1.20.210.7 KB
Original Source

Image Configuration in Helm Chart: add imageRegistry and imageNamespace

<!-- toc --> <!-- /toc -->

Release Signoff Checklist

This checklist contains actions which must be completed before a PR implementing this design can be merged.

  • This design doc has been discussed and approved
  • User-facing documentation has been PR-ed against the release branch in [cert-manager/website]

Summary

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.

Motivation

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.

Goals

  • Support two global values (imageRegistry, imageNamespace) so that users can switch registries and/or namespaces once.
  • Support changing the repository namespace for all images in one place.
  • Don’t break users who use --set webhook.image.repository=ghcr.io/my-cert-manager/my-webhook (or similar repository overrides).

Non-Goals

  • introducing logic to add a digest to image._defaultReference

Existing situation

Today, cert-manager charts treat image.repository as “the full repository path” and (optionally) image.registry as an extra prefix.

Current defaults look like:

yaml
# Current defaults:
image:
  # registry: quay.io
  repository: quay.io/jetstack/cert-manager-controller

And in pseudocode:

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.

Proposal

Introduce two additional Helm values, they will look like:

yaml
# 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.name
  • cainjector.image.name
  • startupapicheck.image.name
  • acmesolver.image.name

Image repository construction for a given component becomes:

pseudocode
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

User Stories (Optional)

Story 1: Global image defaults for mirroring and enterprise registries

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.

Use-case 1: Mirroring with an Artifactory-style path prefix

Users of the Helm chart with Artifactory-mirrored images have to repeat the same thing over and over:

yaml
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:

yaml
imageRegistry: artifactory.domain.com/docker

Resulting repository:

artifactory.domain.com/docker/jetstack/cert-manager-startupapicheck
<---------------------------> <------> <-------------------------->
       imageRegistry        imageNamespace      image.name
Use-case 2: Enterprise registry with a custom namespace
yaml
imageRegistry: private-registry.venafi.cloud
imageNamespace: cert-manager
private-registry.venafi.cloud/cert-manager/cert-manager-startupapicheck
<---------------------------> <----------> <-------------------------->
       imageRegistry         imageNamespace        image.name
Use-case 3: Enterprise registry with FIPS image names

If FIPS images are selected by using -fips in the image name, users still only need to configure the global registry/namespace once.

yaml
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

Coherence with the other cert-manager Helm charts

Here is a comparison of how other cert-manager-related projects structure their Helm values for images:

ProjectWhere are images configured?Existing default for image.registryExisting image.repositoryNotable differences
csi-driverimage.* + per-component imagesno default setquay.io/jetstack/cert-manager-csi-driverPer-component sidecars (node registrar, liveness) with their own image blocks
istio-csrimage.*no default setquay.io/jetstack/cert-manager-istio-csrStandard single image block
google-cas-issuerimage.*""quay.io/jetstack/cert-manager-google-cas-issuerRegistry field exists but default is empty
aws-privateca-issuerimage.*field doesn't existpublic.ecr.aws/k1n1h4h4/cert-manager-aws-privateca-issuerNo registry override field at all
approver-policyimage.*no default setquay.io/jetstack/cert-manager-approver-policyStandard single image block
trust-managerimage.* + defaultPackageImage.*no default setquay.io/jetstack/trust-managerHas global.rbac only; no global image settings
csi-driver-spiffeimage.* with repo map (driver,approver)no default setSee (1)image.repository is a map, not a string
openshift-routesimage.*no default setghcr.io/cert-manager/cert-manager-openshift-routesStandard single image block
cert-manager (proposal)image.* + per-component imagesno default setquay.io/jetstack/cert-manager-csi-driver-spiffeeach 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.,

yaml=
image:
  repository:
    driver: quay.io/jetstack/cert-manager-csi-driver-spiffe
    approver: quay.io/jetstack/cert-manager-csi-driver-spiffe-approver

Risks and Mitigations

  • Risk: Backwards compatibility for users implementing image.registry.
    • Mitigation: Provide a migration path in the documentation and a deprecation period where image.registry is still accepted but warned against.

Alternatives

  • Keep image.registry for backwards compatibility. This will make the Helm templating logic a lot more complex and the Helm chart values.yaml harder to understand.