Dependencies, OCI Registries and Testing Charts: Chart.lock, helm push, lint, helm test and CI
Chart dependencies are declared with a range in Chart.yaml and pinned with Chart.lock; the chart is published to an OCI registry with helm package and helm push, but the registry does not stop you from pushing the same version again with different content. We add an optional Valkey subchart and global.imageRegistry to the Bookstore chart and look with real output at Helm 4's registry login and install-by-digest, kubeconform, helm test, chart-testing and a GitHub Actions workflow that runs them.
Version note: The examples in this post were run with Helm 4.3.2, kind 0.33 with Kubernetes 1.37, kubectl 1.37, kubeconform 0.8, chart-testing 3.15, actionlint 1.7 and .NET 10.
Guess First
You pushed version 0.6.0 of the Bookstore chart to the OCI registry and an environment installed it. The next day you make a small fix in values.yaml but forget to bump the version in Chart.yaml, and run helm push again. Does the registry refuse with "this version already exists"? If not, which content does someone installing with --version 0.6.0 tomorrow get? The answer is in the versioning section, with the real push output.
Dependencies and Chart.lock
A chart can depend on other charts; Helm downloads them into the charts/ folder and renders them together. As a sample dependency for Bookstore, the online bookstore used as the shared example across this site's series, we added the Valkey chart we installed in the first post. Bookstore.Api does not use a cache today; the goal is to see the mechanism, so the dependency is off by default:
dependencies:
- name: valkey
version: "1.0.x"
repository: https://valkey.io/valkey-helm/
condition: valkey.enabled
# Shared with subcharts: every chart can read .Values.global.
global:
# Mirror registry for all images, e.g. registry.example.com/mirror (empty: use the image names as they are).
imageRegistry: ""
...
# Optional dependency (subchart). Its values go under its name; Bookstore.Api does not use it yet.
valkey:
enabled: false
version can be a range: 1.0.x accepts the newest patch in the 1.0 series. condition looks at a boolean in values; if valkey.enabled is false, the subchart is not rendered at all. There are also tags to switch several dependencies on and off together. The command that resolves the range to a real version and downloads the package is helm dependency update:
helm dependency list charts/bookstore
helm dependency update charts/bookstore
cat charts/bookstore/Chart.lock
NAME VERSION REPOSITORY STATUS
valkey 1.0.x https://valkey.io/valkey-helm/ missing
Saving 1 charts
Downloading valkey from repo https://valkey.io/valkey-helm/
Deleting outdated charts
dependencies:
- name: valkey
repository: https://valkey.io/valkey-helm/
version: 1.0.0
digest: sha256:5fa5e0af5553cc2d852e9e55b70d34b3aa9b4992cc44b5fad4625f44f5b3e02e
generated: "2026-11-15T07:28:21.468136+03:00"
Chart.lock records which version the range resolved to and a digest. helm dependency build reads this file and downloads exactly that version; update resolves the range again and updates the lock file. The downloaded .tgz files count as build output and stay out of Git:
# Packaged charts and downloaded dependencies are build output.
*.tgz
Committing Chart.lock
Without a lock file, helm dependency build quietly behaves like update and downloads the newest version in the range; the same commit can produce two different packages on two different days. When the lock file does not match Chart.yaml, it fails openly:
# No Chart.lock: build resolves and downloads the newest version in the range
rm charts/bookstore/Chart.lock
helm dependency build charts/bookstore
# The range in Chart.yaml changed, Chart.lock stayed old
helm dependency build charts/bookstore
Saving 1 charts
Downloading valkey from repo https://valkey.io/valkey-helm/
Deleting outdated charts
Error: the lock file (Chart.lock) is out of sync with the dependencies file (Chart.yaml). Please update the dependencies with 'helm dependency update'
If the dependency was never downloaded, the render stops too; that is why running helm dependency build before helm template in CI is a must:
Error: an error occurred while checking for chart dependencies. You may need to run 'helm dependency build' to fetch missing dependencies: found in Chart.yaml, but missing in charts/ directory: valkey
Subchart Values and global
A subchart's values are written in the parent chart under a key named after the subchart: valkey.resources.requests.memory is the Valkey chart's own resources.requests.memory. A subchart cannot see the parent's values; the one exception is global. Everything under global reaches every chart along the same path. The Valkey chart supports global.imageRegistry; we read the same value in our own helper:
{{- define "bookstore.image" -}}
{{- $repository := required "image.repository is required" .image.repository -}}
{{- with .ctx.Values.global.imageRegistry }}
{{- $repository = printf "%s/%s" (trimSuffix "/" .) $repository }}
{{- end }}
{{- printf "%s:%s" $repository (.image.tag | default .ctx.Chart.AppVersion) -}}
{{- end }}
helm template bookstore charts/bookstore -f deploy/values-dev.yaml --set valkey.enabled=true \
--set global.imageRegistry=registry.example.com/mirror | grep -E "^\s+image: " | sort -u
image: "registry.example.com/mirror/helmseries-bookstore-api:1.2.0"
image: "registry.example.com/mirror/helmseries-bookstore-web:1.2.0"
image: registry.example.com/mirror/valkey/valkey:9.1.3
image: curlimages/curl:8.17.0
A team using a corporate mirror registry can point every image there with one value. The curl image of the test pod stayed outside this rule; fixing it is an exercise in the experiment section. Giving the subchart values works the same way:
helm template bookstore charts/bookstore -f deploy/values-dev.yaml --set valkey.enabled=true \
--set valkey.resources.requests.memory=64Mi | grep "^# Source" | sort -u
# Source: bookstore/charts/valkey/templates/deploy_valkey.yaml
# Source: bookstore/charts/valkey/templates/init_config.yaml
# Source: bookstore/charts/valkey/templates/service.yaml
# Source: bookstore/charts/valkey/templates/serviceaccount.yaml
# Source: bookstore/templates/configmap.yaml
# Source: bookstore/templates/deployment.yaml
# Source: bookstore/templates/service.yaml
# Source: bookstore/templates/serviceaccount.yaml
# Source: bookstore/templates/tests/smoke-test.yaml
# Source: bookstore/templates/web.yaml
A note: in the fourth post we put additionalProperties: false at the root; when a dependency is added, the subchart's key (valkey) must be added to the schema too, otherwise --set valkey.enabled=true fails the schema. The Valkey chart's own values.schema.json validates the subchart's values separately.
Publishing the Chart to an OCI Registry
To share the chart we first package it. The package is a .tgz holding everything not excluded by .helmignore, plus the dependencies under charts/:
helm package charts/bookstore -d dist
Successfully packaged chart and saved it to: dist/bookstore-0.6.0.tgz
In the lab, an OCI registry container that asks for a user name and password runs on 127.0.0.1. In Helm 4, helm registry login accepts only the registry's address; addresses that include a repository path, which worked in Helm 3, now fail:
# Lab registry: 127.0.0.1:52570, plain HTTP; user and password are lab-only test values
echo "$REGISTRY_PASSWORD" | helm registry login http://127.0.0.1:52570/charts -u helm-ci --password-stdin --plain-http
echo "$REGISTRY_PASSWORD" | helm registry login 127.0.0.1:52570/charts -u helm-ci --password-stdin --plain-http
echo "$REGISTRY_PASSWORD" | helm registry login 127.0.0.1:52570 -u helm-ci --password-stdin --plain-http
level=WARN msg="registry login currently only supports registry hostname, not a repository path" host=http://127.0.0.1:52570/charts suggested=http:
Error: invalid reference: invalid registry "http://127.0.0.1:52570/charts"
level=WARN msg="registry login currently only supports registry hostname, not a repository path" host=127.0.0.1:52570/charts suggested=127.0.0.1:52570
Error: invalid reference: invalid registry "127.0.0.1:52570/charts"
Login Succeeded
--plain-http is only for the lab: the registry has no TLS. GHCR or a cloud provider's registry works over HTTPS and does not need this flag. I did not push to a cloud registry or to GHCR for this post; the GitHub Actions workflow below writes that step out, but it was not run on GitHub.
helm push dist/bookstore-0.6.0.tgz oci://127.0.0.1:52570/charts --plain-http
Pushed: 127.0.0.1:52570/charts/bookstore:0.6.0
Digest: sha256:49cf7df10a95…
Helm 4 produces chart archives reproducibly: packaging the same content twice gives byte-for-byte identical files. That means a package built from the same source carries the same digest.
helm package charts/bookstore -d dist2
shasum -a 256 dist/bookstore-0.6.0.tgz dist2/bookstore-0.6.0.tgz
567da13ec613… dist/bookstore-0.6.0.tgz
567da13ec613… dist2/bookstore-0.6.0.tgz
helm show chart oci://127.0.0.1:52570/charts/bookstore --version 0.6.0 --plain-http
Pulled: 127.0.0.1:52570/charts/bookstore:0.6.0
Digest: sha256:49cf7df10a95…
apiVersion: v2
appVersion: 1.2.0
dependencies:
- condition: valkey.enabled
name: valkey
repository: https://valkey.io/valkey-helm/
version: 1.0.x
description: Bookstore API (ASP.NET Core 10) on Kubernetes
name: bookstore
type: application
version: 0.6.0
A security feature Helm 4 brought is installing by digest: you bind to the content hash instead of a tag, and if the digest does not match, nothing is installed.
helm install bookstore oci://127.0.0.1:52570/charts/bookstore@sha256:49cf7df10a95… \
-n bookstore-preview -f deploy/values-dev.yaml --plain-http --wait
helm install other oci://127.0.0.1:52570/charts/bookstore@sha256:0000000000000000000000000000000000000000000000000000000000000000 \
-n bookstore-preview --plain-http
Pulled: 127.0.0.1:52570/charts/bookstore@sha256:49cf7df10a95…
Digest: sha256:49cf7df10a95…
NAME: bookstore
LAST DEPLOYED: Sun Nov 15 07:36:13 2026
NAMESPACE: bookstore-preview
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
...
Error: INSTALLATION FAILED: failed to perform "FetchReference" on source: 127.0.0.1:52570/charts/bookstore@sha256:0000000000000000000000000000000000000000000000000000000000000000: not found
Versioning and Immutability
Back to Guess First. We changed replicaCount in values, left the version at 0.6.0 and pushed again:
# A copy with replicaCount: 3 in values.yaml, version number unchanged
helm package repush -d dist3
helm push dist3/bookstore-0.6.0.tgz oci://127.0.0.1:52570/charts --plain-http
helm show values oci://127.0.0.1:52570/charts/bookstore --version 0.6.0 --plain-http | grep -n "^replicaCount"
Pushed: 127.0.0.1:52570/charts/bookstore:0.6.0
Digest: sha256:f134e0e03d18…
13:replicaCount: 3
The registry said nothing; OCI tags, like container image tags, can be changed. --version 0.6.0 now installs different content, and the environment that installed yesterday and the one installing today run different charts under the same version number. There are three ways to protect yourself: check in CI before the push whether the version already exists in the registry and stop if it does (the workflow below does this), turn on the registry's tag immutability feature (many cloud registries support it), and install the chart by digest in critical environments. The version number is semver: a change that breaks templates is major, a new optional setting is minor, a fix is a patch. We saw in the second post that the chart version and appVersion are independent; the chart can be 0.6.0 while the application is 1.2.0. Version tags and releases in Git are in the twenty-third post of the Git & GitHub series, and versioning strategy is in the Versioning and Release Management series.
Quality: lint, Render and kubeconform
In the fourth post we saw that helm lint turns missing required values into warnings. There is another class lint does not catch: fields that do not match the Kubernetes schema. kubeconform validates the rendered manifests against JSON Schemas generated from Kubernetes' OpenAPI schema; no cluster is needed.
for env in dev staging prod; do
helm template bookstore charts/bookstore -n "bookstore-$env" -f "deploy/values-$env.yaml" \
| kubeconform -strict -summary -kubernetes-version 1.37.0
done
Summary: 7 resources found parsing stdin - Valid: 7, Invalid: 0, Errors: 0, Skipped: 0
Summary: 8 resources found parsing stdin - Valid: 8, Invalid: 0, Errors: 0, Skipped: 0
Summary: 8 resources found parsing stdin - Valid: 8, Invalid: 0, Errors: 0, Skipped: 0
With a copy where a letter is missing in the startupProbe:
# A copy with failureTreshold instead of failureThreshold in the startupProbe
helm lint typo -f deploy/values-dev.yaml | tail -1
helm template bookstore typo -f deploy/values-dev.yaml | kubeconform -strict -kubernetes-version 1.37.0
1 chart(s) linted, 0 chart(s) failed
stdin - Deployment bookstore-api is invalid: problem validating schema. Check JSON formatting: jsonschema validation failed with 'https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.37.0-standalone-strict/deployment-apps-v1.json#' - at '/spec/template/spec/containers/0/startupProbe': additional properties 'failureTreshold' not allowed
Lint passed, kubeconform caught it; without -strict, unknown fields would be accepted silently. When we tried to install the same copy, Helm 4's server-side apply rejected the field too:
Error: INSTALLATION FAILED: server-side apply failed for object bookstore-typo/t-bookstore-api apps/v1, Kind=Deployment: failed to create typed patch object (bookstore-typo/t-bookstore-api; apps/v1, Kind=Deployment): .spec.template.spec.containers[name="api"].startupProbe.failureTreshold: field not declared in schema
So the error does get caught in the end, but at install time, after a release has failed in the cluster. kubeconform shows the same error in the pull request without touching the cluster. For charts that use CRDs, kubeconform needs the CRD schemas passed in separately.
helm test and chart-testing
helm test runs the pods in the chart that carry the helm.sh/hook: test annotation and records the result on the release. We rewrote the test pod we deleted in the second post for Bookstore: it probes the API's readiness endpoint and the catalog.
{{- $api := printf "http://%s-api:%v" (include "bookstore.fullname" .) .Values.service.port -}}
apiVersion: v1
kind: Pod
metadata:
name: {{ include "bookstore.fullname" . }}-smoke-test
labels:
{{- include "bookstore.labels" (dict "ctx" . "component" "test") | nindent 4 }}
annotations:
# Runs only with "helm test"; the pod is deleted before the next run.
"helm.sh/hook": test
"helm.sh/hook-delete-policy": before-hook-creation
spec:
restartPolicy: Never
automountServiceAccountToken: false
securityContext:
{{- include "bookstore.podSecurityContext" . | nindent 4 }}
runAsUser: 100
containers:
- name: smoke
image: curlimages/curl:8.17.0
command: ["sh", "-c"]
args:
- >-
curl -sSf -o /dev/null -m 5 --retry 5 --retry-connrefused --retry-delay 2 {{ $api }}/health/ready &&
curl -sSf -m 5 "{{ $api }}/api/books?pageSize=1" | grep -q '"isbn"' &&
echo "smoke test passed: {{ $api }}"
securityContext:
{{- include "bookstore.containerSecurityContext" . | nindent 8 }}
helm upgrade bookstore charts/bookstore -n bookstore-dev -f deploy/values-dev.yaml --wait
helm test bookstore -n bookstore-dev --logs
...
TEST SUITE: bookstore-smoke-test
Last Started: Sun Nov 15 07:30:18 2026
Last Completed: Sun Nov 15 07:30:27 2026
Phase: Succeeded
POD LOGS: bookstore-smoke-test (smoke)
curl: (28) Connection timed out after 5003 milliseconds
smoke test passed: http://bookstore-api:8080
The timeout line in the log is real: the test ran right after the upgrade and the first try hit the new pod; curl's --retry succeeded on the second try. We saw why that retry is there with chart-testing below.
chart-testing (ct) is the tool the Helm project wrote for chart repos. ct lint finds changed charts, checks that the version was bumped relative to the target branch, validates Chart.yaml and the YAML files, and lints the chart with every values file in the ci/ folder. ct install installs each chart into a temporary namespace, runs helm test and deletes the namespace. The settings and the values for CI:
chart-dirs:
- charts
target-branch: main
validate-maintainers: false
check-version-increment: true
helm-extra-args: --timeout 300s
# chart-testing installs the chart once per file in ci/; no database server in CI.
database:
provider: InMemory
ct looks for the Chart.yaml schema and the yamllint settings under etc/ in its install folder; with the binary installed on its own, the first run gave us this error and we had to copy the files under ~/.ct. The chart-testing action in GitHub Actions does this itself.
Error: failed loading configuration: 'chart_schema.yaml' neither specified nor found in default locations
ct lint --config ct.yaml
Linting charts...
...
bookstore => (version: "0.6.0", path: "charts/bookstore")
...
Checking chart "bookstore => (version: \"0.6.0\", path: \"charts/bookstore\")" for a version bump...
Old chart version: 0.5.0
New chart version: 0.6.0
Chart version ok.
Validating charts/bookstore/Chart.yaml...
Validation success! 👍
Linting chart with values file "charts/bookstore/ci/inmemory-values.yaml"...
==> Linting charts/bookstore
[INFO] Chart.yaml: icon is recommended
1 chart(s) linted, 0 chart(s) failed
...
✔︎ bookstore => (version: "0.6.0", path: "charts/bookstore")
...
All charts linted successfully
We ran ct install on the kind cluster. The first run failed:
ct install --config ct.yaml
Creating namespace "bookstore-hhu5m6jzfx"...
...
deployment "bookstore-hhu5m6jzfx-api" successfully rolled out
deployment "bookstore-hhu5m6jzfx-web" successfully rolled out
...
TEST SUITE: bookstore-hhu5m6jzfx-smoke-test
Last Started: Sun Nov 15 07:29:10 2026
Last Completed: Sun Nov 15 07:29:12 2026
Phase: Failed
...
==> Logs of container bookstore-hhu5m6jzfx-smoke-test
...
curl: (7) Failed to connect to bookstore-hhu5m6jzfx-api port 8080 after 1 ms: Could not connect to server
...
✖︎ bookstore => (version: "0.6.0", path: "charts/bookstore") > failed waiting for process: exit status 1
ct started the test after waiting for the Deployments' rollout, but the test pod could not connect to the Service: the pods were ready, but the Service's endpoints and the kube-proxy rules were a few milliseconds behind. The first idea was adding --wait to helm-extra-args; because ct passes these arguments to helm test as well, another error came up:
Error: unknown flag: --wait
The right fix is to make the test itself resilient: we told curl to retry a few times when the connection is refused.
curl -sSf -o /dev/null -m 5 --retry 5 --retry-connrefused --retry-delay 2 {{ $api }}/health/ready &&
Creating namespace "bookstore-jo87mrr8sa"...
...
TEST SUITE: bookstore-jo87mrr8sa-smoke-test
Last Started: Sun Nov 15 07:29:56 2026
Last Completed: Sun Nov 15 07:29:58 2026
Phase: Succeeded
...
Deleting release "bookstore-jo87mrr8sa"...
release "bookstore-jo87mrr8sa" uninstalled
Deleting namespace "bookstore-jo87mrr8sa"...
namespace "bookstore-jo87mrr8sa" deleted
Namespace "bookstore-jo87mrr8sa" terminated.
...
✔︎ bookstore => (version: "0.6.0", path: "charts/bookstore")
...
All charts installed successfully
Teams that want unit tests for chart templates can look at the helm-unittest plugin: test files written in YAML define checks such as "rendered with these values, this field of the Deployment should be this", and no cluster is needed. I did not install or run this plugin for this post.
In GitHub Actions
The flow has two parts: on a pull request, lint, render, kubeconform, ct lint and ct install on kind; on a chart-v* tag, packaging and a push to GHCR. The workflow sits at the root of the Bookstore repo and runs in the helm/ folder where the chart lives:
name: helm-chart
on:
pull_request:
paths:
- "helm/**"
- "src/**"
- "web/**"
- ".github/workflows/helm-chart.yaml"
push:
tags:
- "chart-v*"
permissions:
contents: read
env:
HELM_VERSION: v4.3.2
jobs:
lint-test:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
defaults:
run:
working-directory: helm
steps:
- uses: actions/checkout@v7
with:
# ct compares the chart with the target branch (version bump check, changed charts)
fetch-depth: 0
- uses: azure/setup-helm@v5
with:
version: ${{ env.HELM_VERSION }}
- uses: helm/chart-testing-action@v2.9.0
- name: Lint and render every environment
run: |
helm dependency build charts/bookstore
for env in dev staging prod; do
helm lint charts/bookstore --strict -f "deploy/values-$env.yaml"
helm template bookstore charts/bookstore -n "bookstore-$env" -f "deploy/values-$env.yaml" > "rendered-$env.yaml"
done
- name: Validate manifests with kubeconform
run: |
curl -sSL https://github.com/yannh/kubeconform/releases/download/v0.8.0/kubeconform-linux-amd64.tar.gz | tar -xz kubeconform
./kubeconform -strict -summary -kubernetes-version 1.37.0 rendered-*.yaml
- name: ct lint
run: ct lint --config ct.yaml --target-branch "$BASE_REF"
env:
BASE_REF: ${{ github.base_ref }}
- uses: helm/kind-action@v1.15.2
with:
node_image: kindest/node:v1.37.0
- name: Build images and load them into kind
working-directory: .
run: |
version=$(yq '.appVersion' helm/charts/bookstore/Chart.yaml)
docker build -f src/Bookstore.Api/Dockerfile --build-arg APP_VERSION="$version" -t "helmseries-bookstore-api:$version" .
docker build -t "helmseries-bookstore-web:$version" web/bookstore-web
kind load docker-image "helmseries-bookstore-api:$version" "helmseries-bookstore-web:$version" --name chart-testing
- name: ct install (install + helm test in a throwaway namespace)
run: ct install --config ct.yaml --target-branch "$BASE_REF"
env:
BASE_REF: ${{ github.base_ref }}
publish:
if: startsWith(github.ref, 'refs/tags/chart-v')
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
defaults:
run:
working-directory: helm
steps:
- uses: actions/checkout@v7
- uses: azure/setup-helm@v5
with:
version: ${{ env.HELM_VERSION }}
- name: Package
run: |
version=$(yq '.version' charts/bookstore/Chart.yaml)
if [ "chart-v$version" != "$GITHUB_REF_NAME" ]; then
echo "::error::tag $GITHUB_REF_NAME does not match Chart.yaml version $version"
exit 1
fi
helm dependency build charts/bookstore
helm package charts/bookstore -d dist
- name: Push to GHCR (never overwrite a published version)
env:
REGISTRY_USER: ${{ github.actor }}
REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
repo="oci://ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/charts"
version=$(yq '.version' charts/bookstore/Chart.yaml)
echo "$REGISTRY_TOKEN" | helm registry login ghcr.io -u "$REGISTRY_USER" --password-stdin
if helm show chart "$repo/bookstore" --version "$version" > /dev/null 2>&1; then
echo "::error::bookstore $version is already published; bump the chart version"
exit 1
fi
helm push "dist/bookstore-$version.tgz" "$repo"
actionlint .github/workflows/helm-chart.yaml && echo ok
ok
I validated this workflow with actionlint; I did not run it on GitHub or with act. I ran its steps (dependency build, lint, render, kubeconform, ct lint, ct install, packaging, push) one by one in the lab above with the same commands; the push to GHCR and the login with GITHUB_TOKEN were not run. Permissions are as small as possible: contents: read per job, packages: write only in the publish job; the details of this model are in the eighth post of the GitHub Actions series, and GHCR in the eleventh. Two steps, which check that the tag matches the Chart.yaml version and that the version has not been published to the registry before, prevent the silent overwrite from Guess First.
In Production
Define dependencies with a range but commit Chart.lock and run build, not update, in CI; a version upgrade should come as a deliberate update commit. Pulling third-party charts in as dependencies carries the supply risk from the first post into your own chart; copying critical dependencies into your own registry and pulling them from there reduces that risk. To sign chart packages there are Helm's provenance files and, in the OCI world, tools such as cosign. Make sure the images ct install installs on kind in CI come from the same build as the images that go to production; otherwise the test validates something else.
Common Pitfalls
Not committing Chart.lock and being surprised when the dependency version changes by itself. Committing charts/*.tgz and carrying packages that contradict the lock file. Pushing the same chart version again with different content. Forgetting to use the subchart's name as the key when giving it values, or thinking the parent's values pass to the subchart by themselves. Giving helm registry login a repository path in Helm 4. Running only helm lint and finding schema errors in production. Writing a timing-sensitive helm test and getting used to a pipeline that turns red now and then in CI.
A Small Experiment
★☆☆ Upgrade the dev release with valkey.enabled=true, see the Valkey pod with kubectl get pods, then confirm in helm get manifest that the subchart's resources are part of the release.
★★☆ Move the test pod's image into values (tests.image) and make it follow global.imageRegistry. Render with --set global.imageRegistry=... and check that every image: line points to the same registry.
★★★ Add a step to the workflow: on a pull request, pull the previous version of the changed chart from the registry, render it with the same values as the new version, and write the diff of the two outputs as a PR comment (to test it without act or GitHub, write the diff to a file first).
Our chart is now a package: versioned, tested and in a registry. In the last post of the series we look at moving from Helm 3 to Helm 4: what changed, what breaks, Helm 3's support timeline and a migration checklist.