What does kubeVersion in a Helm Chart.yaml do, and when is it checked?
answer
- It is a range, not a version
- A gate, not a warning
- What it compares against depends on the command
- A hyphen makes a version a prerelease
- The -0 suffix on every bound
basics
~20 skubeVersion is an optional SemVer range of Kubernetes versions the chart supports. Helm compares it against the version it has in hand whenever it renders the chart and refuses outright if the constraint is not satisfied, rather than warning.
solid answer
~50 s`kubeVersion` in `Chart.yaml` is a SemVer *range* constraint — `>=1.29.0-0 <1.33.0-0`, not a single version — declaring which Kubernetes versions the chart supports. Helm checks it wherever it renders the chart and fails hard when it is not satisfied; it is a gate, not a warning. What it compares against differs by command: `helm install` and `helm upgrade` use the version the live API server reports, while an offline `helm template` uses Helm's built-in default unless `--kube-version` overrides it — so a chart can render green in CI and be rejected on a real cluster, or the reverse. The classic trap is SemVer prerelease matching: a cluster that reports a version with a hyphenated vendor build suffix is, to SemVer, a prerelease, and a plain `>=1.29.0` will not match it. Writing the bound as `>=1.29.0-0` is the idiom that fixes it. Note also that `kubeVersion` says nothing about which API groups actually exist.
code
yaml · 6 linesapiVersion: v2
name: tile-server
version: 2.14.3
appVersion: "7.9.2"
# -0 on both bounds so vendor-suffixed cluster versions still match
kubeVersion: ">=1.29.0-0 <1.33.0-0"go deeper
Recognise kubeVersion as the optional field that states which Kubernetes versions a chart supports, and that Helm refuses to proceed when the cluster does not match rather than warning you.
Explain that it holds a range expression and that Helm evaluates it against the API server's reported version on install or upgrade, but against an assumed version during an offline render unless you say otherwise.
Demonstrate having hit the prerelease rule: know why a hyphenated vendor build suffix fails a plain lower bound and why the -0 idiom fixes it, and know that CI rendering does not exercise the real comparison.
Own the fleet consequence: raising a chart's supported floor is a breaking change to every consumer, so decide how that is versioned, announced and sequenced against the clusters that are still behind.
## A hard gate on the cluster version, expressed as a range `kubeVersion` is an optional `Chart.yaml` field holding a **SemVer range constraint**: the set of Kubernetes versions the chart's author is willing to support. It is a range expression, not a version — `>=1.29.0-0`, `>=1.29.0-0 <1.33.0-0`, `~1.31.0` are all legal, and writing a bare `1.31.2` means "exactly this", which is almost never what you want. When Helm loads a chart it evaluates the constraint against a Kubernetes version and, if the constraint is not satisfied, it stops with an error saying the chart requires that `kubeVersion` and the version in hand is incompatible. There is no warning mode and no partial render: the whole operation fails. ## Which version it compares against This is the part people get wrong: the version Helm has in hand depends on the command. | Command | Version it checks the constraint against | |---|---| | `helm install`, `helm upgrade` | the version the live API server reports | | `helm template` (offline) | Helm's own built-in default, unless `--kube-version` states one | That asymmetry is the source of a whole family of "it worked in CI" incidents: a pipeline that renders and diffs charts offline is checking the constraint against a version that has nothing to do with the clusters you deploy to. If your CI renders charts, pass `--kube-version` and pass the version each target cluster is actually on. ## The prerelease trap SemVer treats anything after a hyphen as a **prerelease**, and by SemVer's own comparison rules a prerelease does not satisfy a constraint that has no prerelease component. Managed Kubernetes distributions commonly report versions with a hyphenated vendor build suffix, which SemVer therefore reads as a prerelease of that patch version. The consequence: `kubeVersion: ">=1.29.0"` rejects a cluster reporting `1.31.4` with a vendor suffix, even though 1.31.4 is obviously newer than 1.29.0. The fix is the widely used idiom of pinning the lower bound with `-0`, the lowest possible prerelease: `>=1.29.0-0`. Do the same on an upper bound — `<1.33.0-0` — or a suffixed 1.32 build will be excluded when you meant to include it. Interviewers like this one because the failure is counter-intuitive, the error message points at the constraint rather than at SemVer, and the person hitting it is usually mid-rollout. ## What `kubeVersion` is not `kubeVersion` constrains the *cluster's version number*. It says nothing about which API groups and kinds are actually served — a version-compatible cluster can still be missing an aggregated API, a CRD-backed kind, or an optional component your chart's templates emit objects for. Charts that need to branch on the presence of an API group do that inside templates with a capability check, not with `kubeVersion`. Nor does `kubeVersion` say anything about the Helm client's own version; nothing in `Chart.yaml` constrains that. ## A worked case A 41-service platform chart is installed once per team namespace — seventeen tenants, several clusters, and a fleet mid-upgrade. Its `Chart.yaml` gains `kubeVersion: ">=1.29.0-0"` because one service's templates began emitting a field that older API servers reject. Two things now happen: 1. Tenants on clusters still at 1.28 get a clean, immediate failure with an actionable message instead of a half-applied release and a confusing validation error from the API server — that is the point of the field, and it is why declaring it is good hygiene rather than bureaucracy. 2. The platform team's own CI, which runs `helm template` to diff every tenant's rendered output, keeps passing regardless, because it is comparing against Helm's default assumption rather than any real cluster; the constraint only bites at install time until someone wires `--kube-version` into that pipeline. ## The operational judgement to show Keep the range honest and as wide as you can genuinely support, always write the bounds with `-0`, raise the floor only when a template genuinely requires it (and cut a chart major version when you do, because raising the floor breaks consumers), and remember that the constraint is enforced against whatever version the tool has in hand — which in an offline render is not your cluster's.
- Why does kubeVersion: ">=1.29.0" reject a cluster reporting 1.31.4 with a vendor build suffix?Because SemVer reads everything after the hyphen as a prerelease identifier, and a prerelease never satisfies a constraint that has no prerelease component of its own — so `1.31.4-<suffix>` is treated as an earlier, unstable build of 1.31.4 and excluded. Writing `>=1.29.0-0` sets the lower bound to the smallest possible prerelease of 1.29.0, which lets suffixed versions match. Apply the same `-0` to upper bounds.
- Your chart needs a resource kind that only exists when an optional component is installed. Is kubeVersion the right tool?No. `kubeVersion` gates on the cluster's version number, and a version-compatible cluster can still be missing the API group entirely — an optional add-on, a CRD-backed kind, an aggregated API. That check belongs inside the template, where Helm exposes the cluster's available API versions, so the chart can render the object only when the group is present. Use `kubeVersion` for "too old to support", not for "this feature may be absent".
- Should raising a chart's kubeVersion floor change the chart's own version?Treat it as a breaking change to the chart's contract: consumers who could install yesterday cannot install today, and the failure is total rather than degraded. Cut a major chart version, say so in the release notes, and leave the previous line installable for the people still on older clusters. Silently raising the floor in a patch release is how a fleet-wide upgrade turns into seventeen simultaneous tickets.
saying these in an interview costs you the question
- Thinks kubeVersion holds a single version, not a range
- Expects a warning rather than a hard failure
- Assumes helm template checks against the real cluster
- Omits -0 and blames the managed cluster's version string
- Uses kubeVersion to test whether an API group exists
- Raises the floor in a patch release of the chart