Skip to main content
RunBook Academy

KubernetesVII · Declarative Resource ManagementDeclarative resource management

Server-side apply — field ownership and conflict resolution

Intermediate⏱ ~18 minkubectl

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

Not yet marked complete on this device.

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:

  1. 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.
  2. 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.
  3. 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:

  1. The API server stores metadata.managedFields with a list of field managers and the fields each one owns.
  2. Conflicts are detected explicitly. If two managers write the same field, the second apply is rejected with a conflict error unless --force-conflicts is supplied.
  3. 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:

  1. Don’t claim the conflicting field. Remove the field from the manifest; SSA will leave the field alone because the other manager owns it.
  2. Force-take the field. --force-conflicts makes your field manager the new owner. The other manager’s claim is removed.
  3. Switch to a different field manager. If your actor is using --field-manager=my-tool and the conflict is with hpa-controller, you can either accept the conflict (do not claim the field) or use --force-conflicts to take ownership.

SSA vs client-side apply

PropertyClient-side applyServer-side apply
Merge locationkubectl workstationAPI server
Field ownershiplast-applied annotationmetadata.managedFields
Conflictssilent overwriteexplicit error
Multiple actorsfragilefirst-class
Field manager nameimplicit (kubectl)explicit
Removed field behaviourremoved if in last-applieddepends on manager
Force take-overnot 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-conflicts only when intentional. Forcing ownership of a field that another controller owns (HPA, cert-manager) breaks the controller.
  • Audit managedFields before 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-Drift covers state management; SSA’s managedFields is the Kubernetes equivalent of Terraform’s state lock.
  • The Ansible course part XXXVI-Ansible-Drift covers 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

  1. Q1. Where does server-side apply store field ownership information?

  2. Q2. Server-side apply silently overwrites fields owned by another manager when there is a conflict.

  3. 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`.

  4. 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-side for GitOps workflows. ArgoCD and Flux use SSA to coexist with cluster controllers.
  • Set --field-manager to a meaningful name. Identify the actor; do not rely on the default kubectl.
  • Do not use --force-conflicts to take ownership of controller-managed fields. Let HPA own spec.replicas; let cert-manager own its annotations; let service mesh own its sidecar fields.
  • Audit managedFields when debugging field overwrites. The list tells you which actor owns which field.
  • Migrate to SSA incrementally. Add --server-side to existing applies; new apply runs will populate managedFields for your field manager.