The problem
Twenty services each writing their own Deployment, probes and security settings means twenty slightly different versions of the same thing. Better: a platform team writes one good base chart, and each service only supplies values. That needs chart dependencies, a place to publish charts, and version numbers that tell users when an update is safe.
What you need to know already: 25.19 (charts, version), 25.21 (values and their layers), 25.2 (semantic versioning), 25.11 (immutable tags and digests), 10.54 (registries).
Words for this lesson
- dependency / subchart - a chart included inside another chart.
- umbrella chart - a chart whose only job is to include other charts.
- version range - a rule like "any 1.3.x" instead of one exact version.
- lock file (
Chart.lock) - the exact versions a range resolved to, so every build uses the same ones. - chart repository - a place charts are published and downloaded from.
Dependencies
A chart can depend on other charts. They are declared in Chart.yaml:
dependencies:
- name: java-service # the chart's name in the repository
version: "~1.3.0" # a SemVer RANGE: >=1.3.0 <1.4.0
repository: https://charts.lab # or "@platform" (a repo added with helm repo add), or oci://...
- name: redis
version: "20.x"
repository: https://charts.lab
condition: redis.enabled # a values path: false = the subchart is skipped entirely
alias: cache # optional: install it under another name
Redis is a popular in-memory key-value store, often used as a cache.
Ranges: ~1.3.0 = patch updates only, ^1.3.0 = anything below 2.0.0, 1.3.x, >=1.2 <2. Pin to a range in Chart.yaml and let Chart.lock hold the exact versions:
helm dependency update ./chart resolve the ranges, download to charts/, WRITE Chart.lock
helm dependency build ./chart download exactly what Chart.lock says (CI uses this)
helm dependency list ./chart name, version, repository, status (ok / missing / wrong version)
update picks the newest versions the ranges allow and records them; build reproduces exactly the recorded ones. Chart.lock goes into git, charts/*.tgz usually does not (the pipeline runs dependency build). A helm install of a chart whose charts/ is missing fails with found in Chart.yaml, but missing in charts/ directory: java-service.
Values for subcharts
The parent passes values to a subchart under the subchart's name (or alias):
# refunds-worker/values.yaml
java-service: # everything here is .Values inside the java-service subchart
image:
repository: registry.lab/pay/payments-api
tag: "1.5.1"
replicas: 2
redis:
enabled: true # read by the parent (condition) AND visible to the subchart
global:
environment: prod # global: is copied into every subchart as .Values.global
This is exactly the shape that broke stage in the incident (25.23) - valid for an umbrella chart, silently ignored for a normal one. A subchart cannot see the parent's values except global, and the parent cannot read a subchart's defaults unless the subchart exports them (import-values, rarely worth it).
Values precedence with subcharts: subchart's values.yaml < parent's section for it < -f files < --set java-service.replicas=3.
Library charts and platform base charts
type: library charts contain only named templates (25.24); they are dependencies that other charts include from (the common chart pattern). The more common setup in banks is an application base chart owned by the platform team: every service depends on java-service (or uses it directly with only values), so probes, security context, labels and resource defaults are the same everywhere and a platform fix ships by bumping one dependency. The price: a breaking change in the base chart (replicas renamed replicaCount in 2.0.0) breaks every service that bumps it without reading the changelog - which is what SemVer is for.
Where charts live
Classic repositories are an HTTP server with index.yaml listing every chart version and its .tgz. helm repo add NAME URL remembers the repository under a short name, repo update downloads its index again, search repo lists what it has, show values prints a chart's default values, pull --untar downloads and unpacks it:
helm repo add platform https://charts.lab
helm repo update # refresh the cached index.yaml
helm search repo platform/java-service --versions
helm show values platform/java-service --version 1.3.2
helm pull platform/java-service --version 1.3.2 --untar
OCI registries store charts as OCI artifacts next to images (ACR, Harbor, GitLab, Artifactory/Nexus). No index, no repo add. helm package makes the .tgz, helm registry login logs in (password from standard input), helm push uploads, and every command takes an oci:// reference instead of a repo name:
helm package ./refunds-worker # refunds-worker-0.1.0.tgz (version from Chart.yaml)
helm registry login registry.lab -u ci-bot --password-stdin
helm push refunds-worker-0.1.0.tgz oci://registry.lab/charts
helm show chart oci://registry.lab/charts/refunds-worker --version 0.1.0
helm upgrade --install refunds-worker oci://registry.lab/charts/refunds-worker --version 0.1.0
The push prints the chart's digest; like images, a chart version is meant to be immutable - most registries can enforce it, and a re-pushed 0.1.0 with different content is the chart equivalent of the mutable-tag incident. The pipeline that promotes an image by digest should promote the chart by version (or digest) the same way: build and push once, deploy the same package everywhere.
Versioning a chart
The chart version follows SemVer and describes the chart's interface (its values and what it renders):
patch 0.4.0 -> 0.4.1 template fix, new appVersion, no values change
minor 0.4.1 -> 0.5.0 new optional values (extraEnv), new templates, defaults unchanged
major 0.5.0 -> 1.0.0 renamed/removed values, changed selectors, anything that needs
users to change their values or reinstall
Changing a Deployment's selector labels is always a major change: the selector is immutable, and the upgrade fails with spec.selector: Invalid value: ...: field is immutable. The fix then is delete-and-recreate (--force-replace, with downtime) or a new release name - both things you do on purpose, not by accident.
helm lint --strict and helm template in CI catch most chart mistakes; helm diff (a Helm plugin - an add-on command; next mission) catches the rest before prod.
What you can now do
- Declare dependencies with version ranges and a lock file, and pass values to a subchart.
- Find, inspect and pull charts from a classic repository, and push and install from an OCI registry.
- Decide whether a chart change is a patch, minor or major version.