skip to content

How do you decide between Chart.yaml `kubeVersion` and `.Capabilities` branching for a chart installed across many cluster versions?

level: principalimportance: should knowfreq 36%

answer

  1. Two different statements about support
  2. One refuses, the other adapts
  3. Every branch is a permutation to test
  4. Prerelease component in the constraint
  5. When branches dominate, cut a major

basics

~20 s

kubeVersion in Chart.yaml is a hard semver gate: Helm refuses to install outside the range. .Capabilities branching keeps one chart usable across a span at the cost of permutations nobody tests. Gate the floor; branch only where behaviour genuinely differs.

solid answer

~50 s

Treat them as two different statements. `kubeVersion` is the support contract: a semver range in `Chart.yaml` that makes Helm refuse an install on a cluster outside it, which fails fast with one clear message and keeps the tested surface small — write the bounds with a prerelease component, such as `>=1.29.0-0`, or clusters reporting a vendor-suffixed version will be rejected wrongly. `.Capabilities` branching is a compatibility mechanism: it keeps a single chart installable across a span, but every branch doubles the renders you should be checking in CI, and unreachable branches rot invisibly. My default is a declared floor plus a small number of branches at the edges — optional objects, not the core workload — and when the branches start to *be* the chart, cut a new chart major version and let consumers stay pinned to the old one.

code

yaml · 7 lines
yaml
apiVersion: v2
name: telemetry-gateway
description: Telemetry ingest gateway
type: application
version: 4.2.0
appVersion: "2.11.3"
kubeVersion: ">=1.29.0-0"

go deeper

for a junior

Know that Chart.yaml can declare a kubeVersion range and that Helm refuses to install the chart on a cluster outside it — that error is a support statement, not a bug in your command.

for a middle

Be able to contrast the two mechanisms: a kubeVersion gate stops the operation entirely, while .Capabilities branching changes what is rendered, and explain why the range bounds carry a -0.

for a senior

Talk about testing and communication: which render permutations CI actually runs, how a stale branch becomes visible, and how a raised floor reaches the teams consuming the chart.

for a principal

Own the support policy — how many Kubernetes minors the chart claims, what raising the floor costs consumers, and when the honest answer is a new chart major rather than another branch.

Both mechanisms answer "which Kubernetes versions does this chart work on?", but they answer it to different audiences and at different moments, and a published chart usually needs both. ### What each one actually does `kubeVersion` in `Chart.yaml` is a semver range evaluated before anything is applied. If the target cluster's reported version falls outside it, Helm refuses the operation and says the chart requires a version the cluster does not satisfy. It is a *declaration*: here is what we support, and we would rather stop than produce something untested. It is checked per operation, so an existing release is never retroactively broken by tightening it — the next upgrade simply stops. `.Capabilities` branching is evaluated during rendering and changes what the chart emits: a different apiVersion, an object included or omitted, a field set or left out. It is an *adaptation*: we support the whole span, and here is how we bend to fit each end of it. ### The costs, honestly A floor costs consumers: someone on an older cluster is simply blocked, and if the floor is aggressive they will fork the chart rather than upgrade a cluster on your schedule. It buys you a small, testable surface and an unambiguous error instead of a mysterious failure three steps later. Branching costs you. Each independent capability test doubles the render permutations that should be exercised, and in practice teams test the version they run and leave the other branch to rot. The failure mode is quiet: a branch that has been broken for a year is discovered by whichever consumer is unlucky enough to be on the old cluster. ### The bounds pitfall Write the range so real clusters match it: ```yaml apiVersion: v2 name: telemetry-gateway version: 4.2.0 appVersion: "2.11.3" kubeVersion: ">=1.29.0-0" ``` The `-0` matters. Clusters routinely report versions carrying a suffix after the patch number, and a semver constraint with no prerelease component excludes anything that has one. A bound of `>=1.29.0` can reject a cluster that is obviously newer than 1.29.0, and the reported error looks like a Helm bug to the person hitting it. Also decide deliberately whether to write an upper bound: it protects you from untested future versions and guarantees that every consumer is blocked the week their platform team upgrades. ### The third and fourth options Branching and gating are not the only choices. You can **cut a new chart major version**: raise the floor there, keep the old line available in the repository, and let consumers pin the `version` they can run. This is often the cleanest answer for a widely-consumed chart, because a consumer's upgrade of the chart and of their cluster become one decision they schedule themselves. You can also **push the decision to the values contract**: instead of detecting, require the consumer to declare what they want, which is honest when the difference is a policy choice rather than a hard capability. ### Making branches survive Whatever you keep, render it. A CI matrix that runs `helm template` once per supported version, with that version's API list, and compares against committed golden output turns a rotten branch into a diff: ```bash for kv in v1.29.11 v1.30.7 v1.31.4; do helm template gateway . --kube-version "$kv" \ --api-versions policy/v1 > "renders/$kv.yaml" done ``` The matrix also documents the support claim: the versions in that loop should be exactly the versions the floor admits. When you raise the floor, the same change should delete the branches below it — otherwise the chart accumulates code for clusters it now refuses to install on, which is the worst of both worlds. ### How I would land it Declare a floor that matches what you actually test, with a prerelease-tolerant bound and usually no upper bound. Branch sparingly, at the edges of the chart rather than in its core workload, and prefer the group/version/kind form of the capability test so the condition says exactly what it needs. Put every branch in the render matrix. Review the floor on a fixed cadence rather than when someone complains, and when raising it, ship it as a chart major with release notes rather than as a quiet metadata edit — the consumers pinning your chart are the ones who will feel it first.

  • How do you retire support for an old Kubernetes minor without breaking existing installs?
    Raise the floor in a new chart major version and say so in the release notes. Existing consumers stay on the previous chart version until their clusters move. Because the gate is evaluated per operation, nothing breaks in place — it only stops the next upgrade to the new chart on an unsupported cluster, which is exactly the signal you want.
  • What does a `kubeVersion` gate not protect you against?
    It compares only the reported server version. It says nothing about which APIs are actually served, so a cluster inside the range can still lack an optional CRD-backed API your chart expects, and a cluster just outside it might work perfectly. It is a support declaration, not a capability check — which is why the two mechanisms coexist.
  • How would you stop capability branches from rotting?
    Render them. A CI matrix of `helm template` runs, one per supported version with that version's API list, compared against committed golden files, turns an unreachable or broken branch into a visible diff. Then make deleting branches below the floor part of the change that raises the floor.

saying these in an interview costs you the question

  • Writes a kubeVersion bound with no prerelease component
  • Branches on capabilities for nearly every object
  • Thinks kubeVersion is enforced continuously after install
  • Keeps branches for versions the chart no longer supports
  • Treats a chart major bump as always unacceptable
  • Claims a version gate proves the needed APIs exist

context