KubernetesVII · Declarative Resource ManagementDeclarative resource management
Server-side apply — field ownership and conflict resolution
What you'll learn
- Explain how server-side apply tracks field ownership
- Use the --server-side, --force-conflicts, and --field-manager flags
- Identify and resolve SSA conflicts
- Decide when to use SSA vs client-side apply
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
Client-side apply puts the merge logic on the kubectl
workstation and uses an annotation (last-applied-configuration)
to track what was declared. Server-side apply (SSA) moves
the merge to the API server and tracks field ownership in a
first-class field on the object (metadata.managedFields).
SSA is the right model when multiple actors — kubectl,
controllers, webhooks, GitOps agents — all need to manage the
same object without overwriting each other.
Why SSA exists
Client-side apply has three problems in production:
- Last-applied-configuration is a hack. The annotation stores what was declared; the merge compares the manifest against it. But the annotation cannot represent partial ownership or concurrent edits.
- Field ownership is implicit. When two tools each apply the same field, the second one overwrites the first. There is no signal that two actors touched the same field.
- kubectl must be online to merge. The merge happens on the kubectl workstation, not on the server. A GitOps controller that runs without kubectl cannot perform the merge.
SSA solves all three:
- The API server stores
metadata.managedFieldswith a list of field managers and the fields each one owns. - Conflicts are detected explicitly. If two managers
write the same field, the second apply is rejected with a
conflict error unless
--force-conflictsis supplied. - Merge logic is on the server. Any actor with API access can issue an SSA request; the server handles the merge.
How SSA tracks field ownership
After an SSA apply, every object has a metadata.managedFields
list. Each entry is one field manager and the fields it
owns:
metadata:
managedFields:
- manager: kubectl
operation: Apply
apiVersion: apps/v1
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:labels:
f:app: {}
f:spec:
f:replicas: {}
f:template:
f:metadata:
f:labels:
f:app: {}
f:spec:
f:containers:
k:{"name":"nginx"}:
f:image: {}
- manager: hpa-controller
operation: Update
apiVersion: autoscaling/v2
fieldsV1:
f:spec:
f:replicas: {} # HPA owns replicas
The HPA controller owns spec.replicas. The kubectl user
owns everything else. If a kubectl user tries to apply a
manifest that declares spec.replicas, the server detects the
conflict: the field is owned by another manager.
flowchart LR
A[kubectl apply] -->|claims| F1[spec.replicas]
B[HPA controller] -->|claims| F1
A --> C[Conflict: field already owned]
B --> D[Owner: HPA wins]
Using kubectl apply with SSA
kubectl apply -f manifest.yaml --server-side
kubectl apply -f manifest.yaml --server-side --force-conflicts
kubectl apply -f manifest.yaml --server-side --field-manager=my-tool
--server-side: switch from client-side apply to SSA.
--force-conflicts: when a conflict is detected, take
ownership of the field anyway. Use with caution.
--field-manager=<name>: identify the actor making the
change. Defaults to kubectl. Set this to a meaningful name
when scripts or controllers do the applying (e.g.,
--field-manager=argo-cd, --field-manager=cert-rotation).
The kubectl.kubernetes.io/last-applied-configuration
annotation is not used in SSA. The annotation is replaced by
metadata.managedFields.
Conflicts and how to resolve them
A conflict occurs when:
- Two field managers claim the same field
- The values they write differ
- Neither manager is willing to give up
The conflict is reported in the apply response. The error message looks like:
error: Apply failed with 1 conflict: conflict with "hpa-controller":
- .spec.replicas: conflicting value "3"; "hpa-controller" has "5"
Three resolutions:
- Don’t claim the conflicting field. Remove the field from the manifest; SSA will leave the field alone because the other manager owns it.
- Force-take the field.
--force-conflictsmakes your field manager the new owner. The other manager’s claim is removed. - Switch to a different field manager. If your actor is
using
--field-manager=my-tooland the conflict is withhpa-controller, you can either accept the conflict (do not claim the field) or use--force-conflictsto take ownership.
SSA vs client-side apply
| Property | Client-side apply | Server-side apply |
|---|---|---|
| Merge location | kubectl workstation | API server |
| Field ownership | last-applied annotation | metadata.managedFields |
| Conflicts | silent overwrite | explicit error |
| Multiple actors | fragile | first-class |
| Field manager name | implicit (kubectl) | explicit |
| Removed field behaviour | removed if in last-applied | depends on manager |
| Force take-over | not supported | --force-conflicts |
Use client-side apply when:
- One actor (kubectl, GitOps controller) manages the object.
- The manifest is the source of truth.
- You want last-applied-configuration to drive the merge.
Use server-side apply when:
- Multiple actors touch the same object (kubectl + HPA + service mesh + cert manager).
- You want explicit conflict detection.
- You want field ownership to survive across actors.
GitOps controllers like ArgoCD and Flux use SSA by default because they need to coexist with controllers (HPA, VPA, service mesh sidecar injection) without overwriting each other.
Migrating from client-side to server-side
If you have been using client-side apply and want to switch to SSA, two steps:
# The object whose annotation you are dropping, as kind/name:
OBJECT=deployment/web
# 1. Tell kubectl to stop writing the annotation
kubectl apply -f manifests/ --server-side --field-manager=gitops
# 2. After all live objects have managedFields entries, you can drop the annotation
kubectl annotate "$OBJECT" kubectl.kubernetes.io/last-applied-configuration-
The first apply under SSA sets the managedFields for the
field manager you specified. Subsequent applies merge against
that state.
Production discipline:
- Pick a field manager name that identifies the actor.
--field-manager=gitops,--field-manager=helm, etc. - Use
--force-conflictsonly when intentional. Forcing ownership of a field that another controller owns (HPA, cert-manager) breaks the controller. - Audit
managedFieldsbefore debugging. If a field is being unexpectedly overwritten, the field manager list tells you who owns it.
Reading managedFields
kubectl get deployment web -o jsonpath='{.metadata.managedFields}' | jq
Output:
[
{
"manager": "kubectl",
"operation": "Apply",
"apiVersion": "apps/v1",
"fieldsV1": {
"f:spec": {
"f:replicas": {},
"f:template": {...}
}
}
},
{
"manager": "hpa-controller",
"operation": "Update",
"apiVersion": "autoscaling/v2",
"fieldsV1": {
"f:spec": {
"f:replicas": {}
}
}
}
]
The HPA controller owns spec.replicas. kubectl owns the rest
of spec. If a kubectl apply declares spec.replicas, the
server detects the conflict and rejects the apply (unless
--force-conflicts is supplied, in which case the HPA’s
ownership is removed and kubectl’s claim is recorded).
Cross-course references
- The Terraform course part
XVII-Terraform-Driftcovers state management; SSA’s managedFields is the Kubernetes equivalent of Terraform’s state lock. - The Ansible course part
XXXVI-Ansible-Driftcovers configuration management; SSA is the cluster-level equivalent of tracking which tool wrote which field. - The GitOps course (CIII-Kubernetes-GitOps) covers ArgoCD and Flux; both use SSA to coexist with cluster controllers.
Quiz
Knowledge check · 4 questions
Q1. Where does server-side apply store field ownership information?
Q2. Server-side apply silently overwrites fields owned by another manager when there is a conflict.
Q3. An HPA controller scales a Deployment from 3 to 5 replicas. An operator then runs `kubectl apply -f deployment.yaml` where the manifest still has `replicas: 3`. With client-side apply, the operator's apply silently reverts the HPA's change. With server-side apply, the apply is rejected with a conflict error. Walk through the SSA flow.
Initial state: Deployment `web` with `replicas: 3` and an HPA `web-hpa` with min=2, max=10 targeting the Deployment. The HPA scales to 5 replicas. The operator's manifest has `replicas: 3`.
Q4. When is server-side apply the right choice over client-side apply?
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Use
--server-sidefor GitOps workflows. ArgoCD and Flux use SSA to coexist with cluster controllers. - Set
--field-managerto a meaningful name. Identify the actor; do not rely on the defaultkubectl. - Do not use
--force-conflictsto take ownership of controller-managed fields. Let HPA ownspec.replicas; let cert-manager own its annotations; let service mesh own its sidecar fields. - Audit
managedFieldswhen debugging field overwrites. The list tells you which actor owns which field. - Migrate to SSA incrementally. Add
--server-sideto existing applies; new apply runs will populatemanagedFieldsfor your field manager.