What does `helm diff upgrade` compare, and how is that different from `helm upgrade --dry-run=server`?
answer
- two previews, two baselines
- one is a plugin, one is built in
- diff compares against the stored manifest
- server dry run runs admission and defaulting
- neither one sees live drift
basics
~20 sThe helm-diff plugin's upgrade subcommand renders the new chart and prints a unified diff against the manifest stored in the release record. A server-side dry-run renders and sends the objects to the API server for validation and admission, printing the manifest rather than a diff.
solid answer
~50 sThey answer different questions. `helm diff upgrade <release> <chart>` — from the helm-diff plugin, not a built-in command — does the same value merge an upgrade would do, renders, and prints a field-level diff against the manifest stored in the current release revision. It tells you *what would change relative to what Helm last applied*, and can be made to exit non-zero when there is any change, so a pipeline can gate on it. `helm upgrade ... --dry-run=server` renders with the same value resolution and sends the objects to the API server as a dry-run: schema validation, defaulting and admission webhooks all run, nothing is persisted and no revision is written. It tells you *whether the cluster would accept this*, and prints the rendered manifest, not a diff. Neither compares against the live objects, so neither sees drift.
code
bash · 8 lines# 1. What would change, versus what Helm last applied
helm diff upgrade reranker-api ./reranker-api -n reco -f prod-values.yaml
# 2. Would this cluster accept the objects at all
helm upgrade reranker-api ./reranker-api -n reco -f prod-values.yaml --dry-run=server
# 3. Has anything drifted since the last upgrade
helm get manifest reranker-api -n reco | kubectl diff -f -go deeper
Know that both commands exist and neither changes the cluster. Be able to say that one prints a diff of what would change and the other prints the manifest after the API server has validated it.
Explain the baselines precisely: the plugin diffs the new render against the manifest stored in the release record, while a server dry run pushes objects through validation, defaulting and admission without persisting anything.
Show how you wire these into a change process: which one gates a pipeline, why a non-deterministic chart destroys the value of a diff, and what neither preview covers — drift, crds/, and rollout health.
Own the tradeoff of depending on a third-party plugin for a review gate, the cost of giving a review pipeline live cluster credentials for server dry runs, and how much preview a change of a given blast radius should be required to carry.
## Three pre-flight questions, three different tools Before a production upgrade there are three separate things you might want to know, and candidates who lump them together get caught: 1. **What would change?** — a field-level delta between the new render and what Helm last applied. 2. **Would the cluster accept it?** — schema validation, defaulting and admission for every object. 3. **Does the cluster still match the record?** — drift introduced since the last upgrade. `helm diff upgrade` answers (1). `--dry-run=server` answers (2). Neither answers (3); that needs the stored manifest compared against live objects. ## helm diff upgrade helm-diff is a CLI plugin, so `helm diff` only exists once someone installs it. Its `upgrade` subcommand takes the same arguments as a real upgrade — release name, chart reference, `-f` files, `--set` overrides, `-n` — performs the same value merge the upgrade would perform, renders the chart, and prints a coloured unified diff. The **baseline is the manifest stored in the current release revision**, not the cluster. That is the single most important sentence about it, and the source of the classic surprise: if someone edited a live ConfigMap by hand, `helm diff upgrade` shows nothing about that edit, because both sides of its comparison come from Helm's own bookkeeping. Because the diff is textual per object, output quality depends on the chart being deterministic. A template that embeds a timestamp, a random suffix, or a checksum over a file that changes for unrelated reasons will show a change on every run and train reviewers to skim past it. Run against a release that does not exist yet, it errors unless you tell it to treat the operation as a first install. It can also be made to exit non-zero when the diff is non-empty, which is what turns it into a CI gate: "this pull request would change these fields on these objects" posted on the change, and a hard stop when a supposedly no-op deploy is not one. ## helm upgrade --dry-run=server A dry run renders the chart exactly as the upgrade would, including the value resolution that depends on the existing release, and then stops short of persisting anything. With `--dry-run=server` it does not stop short of the API server: each object is sent with a dry-run request, so the server runs OpenAPI schema validation, applies defaulting, checks that the kinds and apiVersions actually exist on that cluster, and runs mutating and validating admission. That is what catches a manifest referencing an API version this cluster no longer serves, a field a custom resource's schema rejects, or a policy webhook that will deny the object. What it prints is the rendered manifest plus the release metadata — not a diff. And because admission genuinely runs, a webhook that is not dry-run-safe can make the pre-flight fail on a change that would apply fine, which is worth knowing before you wire it into a gate. Nothing is written: no release revision, no change to the stored manifest, no history entry. The plain client-side form skips the cluster entirely. It renders and does local validation only, so it cannot tell you anything about admission, about kinds the cluster does not have, or about defaults. `helm template` goes one step further and does not consult the release at all — useful for looking at output, useless as a pre-flight for a live release. ## Using them together A realistic pre-flight for a production release is all three, in order: the plugin diff to see the delta a reviewer must approve, a server-side dry run to prove the objects are acceptable to this specific cluster, and a stored-manifest-versus-live diff to confirm nobody has changed the cluster underneath you since the last upgrade. Each is cheap; each catches a different class of mistake. Skipping the third is the common gap, because the first two both take Helm's record as their notion of reality. Two more limits worth stating out loud. Resources installed from `crds/` are never updated by an upgrade, so no preview of the templates says anything about a CRD change. And a preview proves nothing about the rollout being *healthy*: the objects can be accepted, the diff can look exactly as intended, and the new pods can still fail their readiness checks. Previewing is about the change, not about the outcome.
- Your pipeline runs a server-side dry run and it passes, yet the real upgrade is denied by a policy webhook. How?A dry-run request is a different request. Webhooks may branch on it, may be configured to skip it, or the policy may key on something the dry run did not carry. Cluster state also moves between the two calls — a quota filling up, a CRD being replaced, a webhook being reconfigured. Treat a passing dry run as strong evidence, not a guarantee, and keep the window between preview and apply short.
- Why can `helm diff upgrade` report no changes when the chart version in Chart.yaml went up?It diffs rendered output, not chart metadata. A chart bump that only touches `version`, `appVersion` where nothing templates it, comments, or the NOTES file produces byte-identical objects and therefore an empty diff. That is usually the answer you wanted — but if you expected the image tag to move, an empty diff means the value did not reach the template, which is a values-resolution problem rather than a diff problem.
- Does a dry run take the release lock or leave anything behind?No. It renders and, for the server form, issues dry-run requests, then exits. No release revision is written, the stored manifest and values are untouched, and history gains no entry. That is why it is safe to run it from a pipeline against production on every pull request, and why it cannot be used to "reserve" an upgrade.
saying these in an interview costs you the question
- Thinks helm diff is a built-in Helm command
- Says helm diff upgrade compares against the live cluster
- Believes a dry run writes a pending release revision
- Expects --dry-run=server to print a diff
- Thinks the client-side dry run runs admission webhooks
- Assumes a clean preview means the rollout will be healthy