kubectl apply works internally and why it needs three sources of truth to determine what changes to make. Understanding this three-way merge model helps you avoid accidental overwrites and ensures predictable, declarative management of Kubernetes resources.
Quick examples (declarative applies)
nginx.yaml)
- Local configuration: the manifest file on your workstation (for example
nginx.yaml). - Live configuration: the object currently stored in the Kubernetes API (includes
statusand other cluster-managed fields). - Last-applied configuration: a JSON snapshot of your last
kubectl applyrun, stored on the live object as the annotationkubectl.kubernetes.io/last-applied-configuration.
kubectl apply
What happens when you run
kubectl apply
- Create: If the object does not exist,
kubectl applycreates it. When created:- The cluster stores the live configuration.
kubectlwrites the local manifest (converted to JSON) into thekubectl.kubernetes.io/last-applied-configurationannotation on that live object.
- Update: For subsequent runs,
kubectlperforms a three-way comparison between:- the local manifest,
- the last-applied configuration (from the annotation),
- and the live configuration.
Based on that comparison it computes a patch (typically a strategic merge patch for supported resources) and applies only the necessary changes to the live object. After applying,
kubectlupdates the last-applied annotation with the JSON representation of the new manifest.
nginx:1.19 and then run kubectl apply -f nginx.yaml.
Local manifest (updated)
kubectl does:
- Compares local manifest vs last-applied vs live.
- Detects the image change from
nginx:1.18tonginx:1.19. - Computes and applies a minimal patch to update only the image field.
- Updates the
kubectl.kubernetes.io/last-applied-configurationannotation with the JSON of your new manifest.
kubectl to determine whether a field in the live object was introduced by kubectl apply (and should be managed by subsequent applies) or was added directly on the cluster by some other actor.
Concretely:
- If a field is present in the last-applied configuration but missing from your current local manifest,
kubectl applytreats that as an intentional removal and will remove it from the live object. - If a field is present in the live object but not present in either the current local manifest or the last-applied configuration,
kubectl applyassumes that field was added by another actor and generally leaves it unchanged.
type removed)
type: front-end-service, running kubectl apply -f nginx.yaml will remove that label because it existed in the last-applied snapshot but is missing from your current local manifest.
Where the last-applied JSON is stored
The last-applied configuration is saved on the live object itself under the annotation:
kubectl.kubernetes.io/last-applied-configuration
Note that this behavior is specific to kubectl apply. Commands like kubectl create or kubectl replace do not set this annotation unless you replicate the behavior manually.
A few practical notes
Do not mix imperative and declarative operations on the same fields: if you use
kubectl apply to manage a field, avoid making ad-hoc kubectl edit or kubectl patch changes to that same field unless you understand how that will interact with the last-applied snapshot. Mixing approaches can lead to unexpected removals or overrides.- Historically,
kubectl applyimplements a client-side three-way merge using the last-applied annotation. Server-side apply (SSA) is a newer approach that usesmanagedFieldsand different semantics; choose the model that fits your workflow and tooling. - If you rely on automated controllers or operators that modify resource fields, consider using server-side apply or coordinating ownership to avoid friction with declarative
kubectl apply. - For edge cases and full semantics, see the Kubernetes documentation:
kubectl apply, the role of the last-applied configuration, and practical guidance to avoid common pitfalls.