kubectl fundamentals and writing manifests

Declarative beats imperative, a manifest has four top-level keys, and the apply, diff and dry-run loop keeps the cluster matching your files.

Declarative versus imperative

An imperative command tells the cluster what to do once. A declarative file describes what should exist, for ever. Only the second one can be reviewed, versioned, diffed and reapplied — which is the entire reason Kubernetes uses manifests.

StyleExampleUse it for
Imperative, one-shotkubectl run web --image=nginxScratch experiments you will delete in five minutes
Imperative, generated filekubectl create deploy web --image=nginx --dry-run=client -o yamlBootstrapping a manifest without memorising the schema
Declarativekubectl apply -f deploy.yamlEverything that survives the day: the entire job
Live editkubectl edit deploy webEmergency changes only, never as a workflow
# generate a skeleton, then own it as a file
kubectl create deployment web --image=nginx:1.27-alpine --replicas=3 \
  --dry-run=client -o yaml > deploy.yaml

kubectl apply -f deploy.yaml
kubectl get deploy,pods -o wide
kubectl describe deploy web

The anatomy of a manifest

Every object has the same four top-level keys. Once that clicks, you can read an unfamiliar resource by looking at spec and ignoring the rest.

apiVersion: apps/v1        # group/version - published by the server, not invented
kind: Deployment           # what this object is
metadata:                  # identity: name, namespace, labels, annotations
  name: web
  namespace: dev
  labels:
    app.kubernetes.io/name: web
spec:                      # desired state - the only part that varies by kind
  replicas: 3
  selector:
    matchLabels:
      app.kubernetes.io/name: web
  template:
    metadata:
      labels:
        app.kubernetes.io/name: web
    spec:
      containers:
        - name: web
          image: nginx:1.27-alpine
          ports:
            - containerPort: 80
  • apiVersion depends on kind: v1 for Pod and Service, apps/v1 for Deployment and StatefulSet, networking.k8s.io/v1 for Ingress. Get it wrong and you get a clear error — that is a feature.
  • metadata.name plus namespace is the unique key. For most kinds the name must be DNS-safe lowercase.
  • spec is the desired state; status is written by the controller and rejected in your file.
  • Multi-document files separate objects with a line containing only three dashes.
kubectl explain deployment.spec.strategy.rollingUpdate
kubectl explain pod.spec.containers.resources --recursive | head -40

The apply, diff and dry-run loop

Applying a file is the last step, not the first. The loop that keeps you safe is: validate locally, ask the server what is valid, diff against live, then apply.

kubectl apply -f deploy.yaml --dry-run=client       # syntax only, no server
kubectl apply -f deploy.yaml --dry-run=server       # schema + admission, no write
kubectl diff -f deploy.yaml                         # exactly what would change
kubectl apply -f deploy.yaml
kubectl apply -f manifests/ -R --prune -l env=dev   # directory, recursive, prune removed objects
# server-side apply: the server tracks which manager owns which field
kubectl apply --server-side --field-manager=platform -f deploy.yaml
kubectl get deploy web -o yaml --show-managed-fields | less
  • Client-side apply stores the previous file in the kubectl.kubernetes.io/last-applied-configuration annotation. Two people editing the same object with kubectl edit will fight over it.
  • Server-side apply moves field ownership to the API server, so different teams can own different fields without clobbering each other.
  • A field added by hand to a live object and absent from your file is left alone by client-side apply, and may be removed by server-side apply depending on ownership.
  • kubectl diff exits 1 when there is a difference, which makes it a perfect CI gate before apply.
💡
Client-side apply records the previous file in a single annotation on the object, so two people editing overlapping fields overwrite each other. Server-side apply moves that bookkeeping into the API server, tracks each field's manager, and is the better default on any cluster more than one person deploys to.

FAQ

Why does apply say the object already exists?
It was created imperatively or with a different file and lacks the required label or annotation. Adopt it with kubectl apply --server-side, or delete and recreate if it holds no state.
Should I use <code>kubectl edit</code> in production?
Only for an emergency, and always followed by editing the manifest to match. Anything else produces a cluster that no file describes, which is exactly the state manifests exist to prevent.

Cluster architecture and a local setup Labels, selectors, namespaces and annotations

Last refreshed 2026-09-18.