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.
| Style | Example | Use it for |
|---|---|---|
| Imperative, one-shot | kubectl run web --image=nginx | Scratch experiments you will delete in five minutes |
| Imperative, generated file | kubectl create deploy web --image=nginx --dry-run=client -o yaml | Bootstrapping a manifest without memorising the schema |
| Declarative | kubectl apply -f deploy.yaml | Everything that survives the day: the entire job |
| Live edit | kubectl edit deploy web | Emergency 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 webThe 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: 80apiVersiondepends onkind:v1for Pod and Service,apps/v1for Deployment and StatefulSet,networking.k8s.io/v1for Ingress. Get it wrong and you get a clear error — that is a feature.metadata.nameplus namespace is the unique key. For most kinds the name must be DNS-safe lowercase.specis the desired state;statusis 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 -40The 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-configurationannotation. Two people editing the same object withkubectl editwill 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 diffexits 1 when there is a difference, which makes it a perfect CI gate beforeapply.
FAQ
Why does apply say the object already exists?
kubectl apply --server-side, or delete and recreate if it holds no state.Should I use <code>kubectl edit</code> in production?
Related
Cluster architecture and a local setup Labels, selectors, namespaces and annotations
Last refreshed 2026-09-18.