Declarative vs. imperative

Declarative vs. imperative

Three ways to manage objects. Use one per object - mixing them is where drift comes from.

TechniqueExampleYou sayGood for
Imperative commandskubectl run, create deploy, scaledo thisexploring, one-offs
Imperative object configkubectl create -f, replace -fcreate exactly this filerarely
Declarative object configkubectl apply -fmake it look like thiseverything you keep
kubectl create deploy web --image nginx:1.27     # fails if it exists
kubectl apply -f web.yaml                        # creates or updates - idempotent

The workflow

graph LR G["generate<br/>--dry-run=client -o yaml"] --> E[edit YAML] E --> D["kubectl diff -f"] D --> A["kubectl apply -f"] A --> V[version in git]
kubectl create deploy web --image nginx:1.27 --dry-run=client -o yaml > web.yaml
kubectl diff -f web.yaml                  # what would change?
kubectl apply -f web.yaml --dry-run=server  # validated by the API, not stored
kubectl apply -f web.yaml

How apply knows what to remove

apply compares three things: your file, the live object and the last applied configuration. Client-side apply stores the last one in the annotation kubectl.kubernetes.io/last-applied-configuration.

Server-side apply (kubectl apply --server-side) moves that bookkeeping to the API server (metadata.managedFields) and reports conflicts when two managers own the same field. Helm, Argo CD and controllers use it.

Warning

kubectl edit or kubectl scale on an object you manage with apply creates drift: the next apply from git silently reverts it.

References