Sign in
← Kubernetes: configuration
6 min · medium · Lesson 4 of 8

Immutable Secret: blocking changes to data

What the immutable flag does on a Secret or ConfigMap, why it's useful, and how to rotate data when edits are blocked.

Why immutable

Secret and ConfigMap have an immutable field. It's been stable since Kubernetes 1.21. Set immutable: true and the apiserver refuses to change data, stringData, and binaryData. The only way to change the data is to delete the object and create it again.

Why you'd want this:

  1. Protection from accidental edits. A config value or key can't "drift" through a stray kubectl edit secret prod.
  2. Less load on kube-apiserver. For every mutable Secret and ConfigMap mounted into a Pod, kubelet watches it so it can update the files in the volume. For an immutable object the watch is closed: the data won't change, so there's nothing to watch for. On a cluster with thousands of Pods, that's a real saving in API requests.
  3. Immutable deploys. CI generates a unique name (app-config-abc123), the deploy references it, and after a rotation a new app-config-def456 is created. The old one is deleted once nothing references it anymore.

The manifest

apiVersion: v1
kind: Secret
metadata:
  name: db-password
type: Opaque
immutable: true
data:
  password: c2VjcmV0MTIz

data is always base64 (here c2VjcmV0MTIz is secret123). stringData is written as plain text, and the apiserver encodes it on save. When you read the object back, stringData is gone and the value lives in data.

You don't have to set immutable at creation time. You can turn it on later on an existing Secret or ConfigMap, and the object isn't recreated in the process. But you can't turn it back off: the transition true → false is forbidden. To change the data, you recreate the object.

What's forbidden

Any attempt to change the data ends with the same apiserver error:

kubectl patch secret db-password \
  --type=merge -p '{"stringData":{"password":"new"}}'
The Secret "db-password" is invalid: data: Forbidden: field is immutable when `immutable` is set

stringData turned into data in the error: the apiserver moves it into data first, then validates. kubectl apply of the same file with a different password gets the same answer. kubectl edit won't save the change either: it prints error: secrets "db-password" is invalid and Edit cancelled, no valid changes were saved.

Forbidden: changing data, stringData, binaryData, and switching immutable: true → false. For a ConfigMap the error is the same, just with its name and field (data or binaryData).

Allowed: editing metadata.labels and metadata.annotations, for example kubectl label secret db-password team=platform. You can also still delete the object and create it again, that's two separate API requests.

A controller that updates a Secret in place on its own (that's how external-secret sync operators work) runs into the same Forbidden. Either you don't turn on immutable for Secrets like that, or you rotate them through a new name.

Updating an immutable Secret

1. new Secret db-password-v2 2. edit reference secretRef.name: v2 3. rollout new Pods read v2 4. delete v1 no more references time
The data isn't edited in place. The name changes, then the reference, then the Pod.

The main pattern is rotation through a new name. The old Secret stays untouched, a new one is created next to it:

apiVersion: v1
kind: Secret
metadata:
  name: db-password-v2
immutable: true
type: Opaque
data:
  password: bmV3LXNlY3JldA==

In the Deployment you update the reference, it used to be db-password-v1:

envFrom:
  - secretRef:
      name: db-password-v2

Editing the Pod template triggers a rollout: the Deployment creates new Pods, and they read db-password-v2 already. The old Secret is deleted last, once no replica is looking at it anymore. Delete it too early and Pods that get recreated with the old template won't start: status CreateContainerConfigError and event secret "db-password-v1" not found.

A cruder alternative: kubectl delete secret followed right away by kubectl create secret with the same name. Running Pods won't see the new data. Environment variables are read once, at container start, and kubelet doesn't update the files in the volume for an immutable object, because it isn't watching it. The new values only show up in Pods that get recreated. In the gap between delete and create, any new Pod crashes with CreateContainerConfigError. That's why rotating through a new name and a rollout is the cleaner way.

Common mistakes

What you'll need for the tasks

Homework

Practice the theory on your own cluster: 2 steps with a check. Set up the cluster with the Kubernetes locally track, the tasks live in the k8s-practice repository.

./run.sh start k8s-immutable-secret     # set up the starting state
./run.sh verify k8s-immutable-secret    # check the current step

In short

Done reading?

Mark the lesson as read to move your track progress forward.

Go through it with an AI agent

The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.

Get access for $49

Tell the agent: use MCP izi, give me lesson k8s-config-04-immutable-secret