skip to content

Why does Helm 4's --server-side=auto leave a release installed by Helm 3 applying client-side?

level: middleimportance: should knowfreq 44%

answer

  1. Install and upgrade do not share a default
  2. Boolean on install, string on upgrade
  3. auto means inherit, not decide
  4. The previous revision decides the write path
  5. Flip it per release with --server-side=true

basics

~20 s

On helm upgrade and helm rollback, Helm 4's --server-side is a string defaulting to auto, which inherits the apply method recorded for the previous revision. A revision written by Helm 3 was applied client-side, so the release keeps the old three-way merge until you ask for server-side explicitly.

solid answer

~50 s

Server-side apply is Helm 4's default **for new installs**: on `helm install`, `--server-side` is a boolean defaulting to `true`. On `helm upgrade` and `helm rollback` it is a *string* whose default is `auto`, and `auto` means "do what the previous revision did". A release whose last revision was written by a Helm 3 client was written with the client-side three-way strategic merge, so `auto` keeps it there — upgrading the CLI does not silently change the write path of everything already running. That is deliberate: flipping a live release to server-side apply means Helm starts taking field ownership as a field manager, which can collide with fields another controller or a human `kubectl` edit already owns. You opt in per release with `--server-side=true`, and you should treat that first server-side upgrade as a change in its own right rather than as a no-op.

code

bash · 8 lines
bash
# redis-cache was installed by Helm 3 and sits at revision 7
helm history redis-cache -n payments

# no apply flag: auto inherits revision 7's method, so this stays client-side
helm upgrade redis-cache ./redis -n payments

# deliberate flip: this release now applies server-side
helm upgrade redis-cache ./redis -n payments --server-side=true

go deeper

for a junior

Know that Helm 4 prefers server-side apply for new installs but does not retroactively change how an existing release is written, and that there is a flag to ask for it explicitly.

for a middle

Be able to state both defaults precisely — boolean true on install, the string auto on upgrade and rollback — and explain that auto inherits the previous revision's method rather than choosing one.

for a senior

Show judgement about the flip itself: what field-ownership conflicts can surface on the first server-side upgrade of a long-lived object, and how you stage and verify that change on a real workload.

for a principal

Own the fleet-level policy: whether existing releases are migrated to server-side apply deliberately or left inherited indefinitely, who decides per workload, and how you keep the two write paths from becoming permanent hidden state.

## Two defaults, not one The sentence "server-side apply is the default in Helm 4" is true and incomplete, and the incompleteness is the whole question. There are two different defaults: - **`helm install --server-side`** is a **boolean** that defaults to `true`. A release created by a Helm 4 client is applied server-side from revision 1. - **`helm upgrade --server-side`** and **`helm rollback --server-side`** take a **string**, and the default is `auto`. `auto` does not mean "decide cleverly per resource"; it means **inherit the apply method of the previous revision**. The consequence follows directly. Take a Redis chart with a StatefulSet and a PersistentVolumeClaim, installed as `redis-cache` under Helm 3 and now sitting at revision 7. You replace the CLI with Helm 4 and run `helm upgrade redis-cache ./redis -n payments` with no apply flags. Revision 7 was written client-side, so `auto` resolves to client-side, and revision 8 is written the same way. Nothing about the write path changed just because the binary did. ## Why the two paths differ **The client-side three-way strategic merge** is the Helm 3 path. Helm computes a patch by comparing three documents: the manifest stored in the previous release record, the manifest just rendered, and the live object read from the API server. That third input is what lets Helm notice a field a human added by hand and decide whether to keep it. The decision is made *in the client*, and the cluster sees only the resulting patch. **Server-side apply** moves that decision into the API server. The client sends the full intended object and a field-manager identity; the API server records, per field, which manager owns it. Ownership is real state stored in the object's `metadata.managedFields`. If two managers set the same field to different values, the apply is rejected as a conflict unless the caller explicitly asks to override it — and Helm 4 has a distinct flag for exactly that override, separate from the flag that replaces resources outright. Those are genuinely different models, which is why Helm does not switch a live release between them behind your back. ## What flipping actually costs When you do pass `--server-side=true` on an upgrade of a long-lived release, the first apply is where any surprises land: - Fields that were set by something else — another controller, a mutating admission step, or an engineer's `kubectl edit` — now have an owner recorded, and Helm's claim on them can conflict. - Fields Helm previously set and then stopped rendering behave differently: under server-side apply, removing a field from your applied configuration relinquishes it, which is a cleaner model than the client-side one but not an identical one. - Objects that carry a large last-applied-configuration annotation from years of client-side applies do not clean themselves up just because you switched. None of that is a reason to stay client-side forever. It is a reason to schedule the flip as its own change: one release at a time, on a workload whose blast radius you can afford, with the rendered output previewed and the live object diffed before and after. ## Ambiguity worth naming "Ownership" is the sharpest overloaded word here, and an interviewer may be testing whether you notice. There are three distinct mechanisms and this question touches two of them: **release ownership**, the `app.kubernetes.io/managed-by: Helm` label plus the `meta.helm.sh/release-name` and `meta.helm.sh/release-namespace` annotations that tell Helm an object belongs to a particular release; and **field ownership**, the server-side-apply field-manager bookkeeping described above. (The third, Kubernetes `ownerReferences` and garbage collection, is a different thing again and Helm does not use it to track release membership.) Switching apply method changes the second and leaves the first alone: the release still owns the same objects, it just negotiates individual fields differently. ## How to check where a release stands The honest answer is that you check the object, not the flag. `kubectl get <kind> <name> -o yaml --show-managed-fields` shows whether an apply-mode field manager is recorded for the fields your chart renders. Combined with `helm history`, that tells you which revisions were written by which client. The practical operational rule for a fleet move is simpler: assume every release that predates the CLI upgrade is client-side until you have deliberately moved it, and record the flip in the same place you record any other production change. ## The trap answer The answer to avoid is "Helm 4 uses server-side apply, so my old release is now server-side too." It is a reasonable inference from the release notes and it is wrong, and the failure it produces is subtle rather than loud: you plan around field-ownership semantics that are not actually in effect, and the drift behaviour you observe after a manual `kubectl edit` is the old three-way-merge behaviour instead.

  • What could actually go wrong the first time you flip a long-lived release to server-side apply?
    Field conflicts. Once Helm applies server-side, the API server records per-field ownership, and any field another controller, a mutating webhook or a human `kubectl edit` already claims can be reported as a conflict rather than silently overwritten. Fields you stop rendering are also relinquished rather than merged away. Treat the first server-side upgrade of an old release as a real change: preview the render, diff the live object, and start with a workload whose blast radius you can absorb.
  • How would you tell whether a given release is currently applying client-side or server-side?
    Inspect the objects rather than the command line. `kubectl get <kind> <name> -o yaml --show-managed-fields` reveals whether an apply-mode field manager owns the fields your chart renders; `helm history` tells you which revisions predate the CLI upgrade. For a fleet, the workable rule is to assume every release created before you moved to Helm 4 is client-side until you have explicitly flipped it and recorded that you did.
  • Does switching apply method change which objects the release owns?
    No — those are two different notions of ownership. Release membership is tracked by the `app.kubernetes.io/managed-by: Helm` label plus the `meta.helm.sh/release-name` and `meta.helm.sh/release-namespace` annotations, and it is untouched by the apply method. What changes is field ownership: the API server's per-field field-manager bookkeeping, which only exists once you apply server-side.

It is like changing your default currency in a shop's settings: new orders use it, but an order already open keeps the currency it was opened in until someone reopens it deliberately.

saying these in an interview costs you the question

  • Says every release becomes server-side the moment you upgrade the CLI
  • Thinks auto picks per resource based on cluster support
  • Believes --server-side is boolean on upgrade as well as install
  • Confuses field ownership with the release-ownership annotations
  • Treats the first server-side upgrade of an old release as a no-op
  • Assumes Helm rewrites past revisions to the new apply method

context