Christian Lehnert — Linux, Hacking & Faith

Stop Writing Kubernetes YAML by Hand

Christian Lehnert2026-07-25~4 min read

Stop Writing Kubernetes YAML by Hand

There is no reason to type a Pod manifest from memory. kubectl
already knows the schema, and it will write the file for you:

1kubectl run nginx --image=nginx --dry-run=client -o yaml > nginx-pod.yaml

--dry-run=client means "render the object, do not send it
anywhere." -o yaml prints it instead of describing it. You get a
valid manifest, correct apiVersion, correct nesting, in one
command.

The reason this matters is not typing speed. It is that the parts
people get wrong from memory are exactly the parts the generator gets
right every time: whether it is apps/v1 or batch/v1 or
networking.k8s.io/v1, and how many levels down
spec.template.spec.containers actually sits. Those mistakes produce
error messages that send you to the docs for five minutes. The
generator does not make them.

The Rest of the Generators

kubectl run is the one everyone knows. The others cover most of
what you write day to day:

 1# Deployment
 2kubectl create deployment web --image=nginx --replicas=3 \
 3  --dry-run=client -o yaml
 4 
 5# Service (or expose an existing resource)
 6kubectl create service clusterip web --tcp=80:8080 --dry-run=client -o yaml
 7kubectl expose deployment web --port=80 --target-port=8080 \
 8  --dry-run=client -o yaml
 9 
10# Jobs and CronJobs
11kubectl create job backup --image=alpine --dry-run=client -o yaml
12kubectl create cronjob backup --image=alpine --schedule="0 3 * * *" \
13  --dry-run=client -o yaml
14 
15# ConfigMaps and Secrets
16kubectl create configmap app-config --from-file=./config/ \
17  --dry-run=client -o yaml
18kubectl create secret generic db-creds --from-literal=user=app \
19  --dry-run=client -o yaml

RBAC is where this pays for itself most. Role and binding YAML is
tedious and easy to get subtly wrong:

1kubectl create role reader --verb=get,list,watch --resource=pods \
2  --dry-run=client -o yaml
3kubectl create rolebinding reader-binding --role=reader \
4  --serviceaccount=default:myapp --dry-run=client -o yaml

Because you type this constantly, it is worth an alias:

1export do='--dry-run=client -o yaml'
2kubectl create deployment web --image=nginx $do > deploy.yaml

Client vs Server: The Part Worth Knowing

--dry-run=client renders locally. It never contacts the cluster,
so it validates nothing beyond kubectl's own schema knowledge.

--dry-run=server sends the object to the API server, which runs it
through validation and admission control and returns the object as it
would have been persisted — then discards it. Nothing is created.

That difference is useful twice. It catches things client-side
rendering cannot: an invalid field, a CRD that does not exist, an
admission webhook that would reject your object, a policy controller
that would deny it. And it shows you every field the API server
defaults in, which is how you find out what your manifest actually
becomes rather than what you wrote.

It also makes a good CI gate on manifests you already have:

1kubectl apply -f manifests/ --dry-run=server

Validates the whole directory against the real cluster, changes
nothing. Run it on every pull request that touches manifests.

Clean Up the Output

Generated manifests carry cruft that should not be committed:
creationTimestamp: null, an empty status: {}, empty
resources: {}. Strip them. They are noise in review and some of
them are meaningless in a manifest you hand-maintain.

More importantly, the generator gives you a skeleton, not a
production manifest. It will not add resource requests and limits,
liveness and readiness probes, a securityContext that drops
capabilities and runs non-root, or the labels your tooling expects.
Those are the parts that need thought, and they are exactly the parts
worth spending your attention on instead of remembering YAML nesting.

That is the actual argument. The generator handles the mechanical
part correctly and instantly, which frees you to think about the part
that decides whether the workload is any good.

The Takeaway

Never type a manifest from memory. Generate the skeleton with
--dry-run=client -o yaml, validate against the real cluster with
--dry-run=server, delete the generated cruft, and put your effort
into limits, probes, and security context.

Tagged:
#kubernetes #kubectl
← Back to posts