Version-controlled manifests are how real teams run Kubernetes; imperative commands don't scale.
Every object in a manifest has four top-level fields:
apiVersion: the API group and version, such asv1(core) orapps/v1.kind:Pod,Deployment,Service...metadata:name,namespace,labels,annotations.spec: the desired state. The cluster addsstatus, the observed state, which you never write.
Several objects can share a file, separated by ---.
kubectl apply -f is declarative: it creates objects that don't exist and updates those that do, changing only fields you manage. Apply a whole directory with -f k8s/ (add -R for subdirectories). Keep the files in git and you have a reviewable history of your infrastructure.
Useful habits:
kubectl diff -f k8s/shows what apply would change.kubectl apply --dry-run=server -f x.yamlvalidates against the real API without saving.- Generate a starting manifest instead of typing it:
kubectl create deployment web --image=nginx --dry-run=client -o yaml > web.yaml.
Indentation matters, lists use - , and field names are camelCase. A typo in a field name is rejected by the API server's strict field validation, which is on by default and reports unknown fields.
Example
# web.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
labels: {app: web}
spec:
replicas: 2
selector:
matchLabels: {app: web}
template:
metadata:
labels: {app: web}
spec:
containers:
- name: nginx
image: nginx:1.27
# shell:
# kubectl diff -f web.yaml # preview
# kubectl apply -f web.yaml # deployment.apps/web created
# (edit replicas: 3) kubectl apply -f web.yaml # deployment.apps/web configuredRemember
- apiVersion, kind, metadata, spec; status is written by the cluster
- apply creates or updates; diff previews; dry-run=server validates
- Generate skeletons with --dry-run=client -o yaml
Watch out for
- Editing live objects with kubectl edit and then losing the change on the next apply from git.