kubectl cheatsheet (2026 edition)
A working kubectl reference for Kubernetes 1.36: output formats, JSONPath, debug workflows, server-side apply, drain safety, and the version skew rules.
Ben Ennis
Published July 27, 2026
Most kubectl cheatsheets are a dump of every subcommand in alphabetical order, which is useless when you are mid-incident. What follows is organized the way you actually work: find the object, understand the object, change the object, debug the object, then get off the node safely. The official reference is exhaustive and worth bookmarking separately; this is the opinionated subset, plus the behavior changes worth knowing about in the current release.
What is different in 2026
Kubernetes ships three minor releases a year and supports the three most recent minors at any time. As of this writing that is v1.34, v1.35, and v1.36, with v1.36 (“Haru”) released on 22 April 2026 according to the release announcement and the releases page. Each minor gets roughly a year of patch support, so a cluster more than three minors behind is running unpatched control-plane code.
Version skew is the rule people break most often. The version skew policy states that kubectl is supported within one minor version, older or newer, of kube-apiserver. A v1.36 kubectl against a v1.33 API server is out of support, and the symptoms are not a clean error message: you get missing fields, silently dropped flags, and confusing validation output. If you administer clusters on different versions, install per-cluster binaries rather than one global kubectl.
Two v1.36 changes are worth putting in muscle memory. kubectl kuberc
graduated to beta, which means the preferences file described in the
kuberc documentation
is now a supported way to set default flags and aliases without wrapping kubectl
in shell functions. And kubectl diff gained a --show-secret flag, so
diffing a Secret no longer forces you to choose between redacted output and
piping YAML through your own diff. Both changes are listed in the
v1.36 changelog.
Context and namespace, once, correctly
Almost every kubectl mistake with real consequences starts with the wrong
context. Make the current context visible in your shell prompt, and set the
namespace on the context rather than typing -n every time.
kubectl config get-contexts
kubectl config use-context prod-eu-west-1
kubectl config set-context --current --namespace=payments
kubectl config view --minify --output 'jsonpath={..namespace}'
Kubeconfig files merge. If KUBECONFIG lists several paths separated by
colons, kubectl builds one logical config from all of them, and the first
occurrence of a duplicated key wins — the details are in the
kubeconfig documentation.
This is why a stale file in ~/.kube/config can shadow the credentials your
CI helper just wrote.
Finding objects
kubectl get with a good output format answers most questions without opening
a dashboard.
# Wide output plus node placement, sorted by restart count
kubectl get pods -o wide --sort-by='.status.containerStatuses[0].restartCount'
# Everything not in a healthy phase, across all namespaces
kubectl get pods -A --field-selector='status.phase!=Running'
# Only the fields you care about
kubectl get pods -o custom-columns='NAME:.metadata.name,NODE:.spec.nodeName,IMAGE:.spec.containers[*].image'
# Machine-readable, for scripts
kubectl get deploy -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.replicas}{"\n"}{end}'
Two things trip people up here. Field selectors are not label selectors and
they are not general-purpose: the API server only supports a small set of
indexed fields per resource type, documented under
field selectors.
status.phase and metadata.name work on pods; arbitrary paths do not. And
kubectl’s JSONPath is its own dialect with documented differences from the
original spec, described in the
JSONPath support page —
notably {range} and {end} for iteration, which no other JSONPath
implementation has.
Labels are the primary selection mechanism and the one most teams
under-invest in. A cluster where every workload carries app.kubernetes.io/name,
app.kubernetes.io/component, and an owner label is a cluster you can query
during an incident; a cluster where labels were an afterthought forces you into
name-prefix guessing. The
well-known labels reference
lists the names Kubernetes itself and most controllers understand, and it is
worth conforming to them rather than inventing a local scheme.
kubectl get pods -l 'app.kubernetes.io/name=checkout,environment in (prod,canary)'
kubectl get pods -l '!app.kubernetes.io/managed-by'
kubectl label deploy/checkout owner=payments-team --overwrite
kubectl annotate deploy/checkout change-ticket=CHG-4821 --overwrite
Set-based selectors (in, notin, and existence checks with !) are handled
by the API server, so they cost nothing extra and they scale. Object names are
constrained more tightly than people expect: most resources require a
lowercase RFC 1123 subdomain, which is why an underscore in a Deployment name
fails validation while the same string works fine as a label value. The
object names page
lists the rules per resource type.
For “what is happening right now”, kubectl events beats kubectl describe
because it sorts and filters properly:
kubectl events --for pod/checkout-7d8f9 --watch
kubectl events -A --types=Warning
The kubectl events reference covers the filters. Remember that events are ephemeral; the default retention is one hour, so “no events” during a post-incident review means nothing.
Understanding a resource without leaving the terminal
kubectl explain reads the OpenAPI schema from your actual API server, which
makes it more trustworthy than any web reference for the cluster in front of
you.
kubectl explain pod.spec.tolerations
kubectl explain deployment.spec.strategy --recursive
kubectl api-resources --namespaced=true --verbs=list
kubectl api-versions
That last pair is how you discover what CRDs a cluster has and what their short names are. See the kubectl explain reference for the flag list.
Changing things without surprises
The declarative path is apply, and in 2026 there is little reason not to use
server-side apply for anything a controller or a second human might also touch.
kubectl diff -f deploy.yaml
kubectl apply --server-side --field-manager=ci-pipeline -f deploy.yaml
kubectl apply -k overlays/prod/
kubectl rollout status deploy/checkout --timeout=180s
kubectl rollout undo deploy/checkout
Server-side apply
moves merge logic into the API server and records field ownership, so two
actors editing the same object produce an explicit conflict instead of a silent
overwrite. When you do hit a conflict, --force-conflicts takes ownership;
read the conflict message first, because it names the other manager.
Kustomize is built in, so -k works without a separate binary; the
kustomization task page
is the reference. For quick one-offs, generate YAML rather than creating
objects imperatively:
kubectl create deployment web --image=nginx:1.27 --dry-run=client -o yaml > web.yaml
kubectl create job --from=cronjob/nightly-report manual-run-2026-07-27
The second command is the safe way to test a CronJob: it creates a Job from the CronJob template immediately, without touching the schedule.
When you need to block until something is true, use kubectl wait rather than
a sleep loop. It watches the object and exits non-zero on timeout, which makes
it usable in CI:
kubectl wait --for=condition=Available deploy/checkout --timeout=120s
kubectl wait --for=delete pod/migration-job-xyz --timeout=60s
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded job/db-migrate --timeout=10m
The jsonpath form is the general escape hatch and works against custom
resources, which is useful when a CRD does not publish standard conditions.
One warning about kubectl apply --prune: it deletes objects that match its
selector and are absent from the input, which sounds like the reconciliation
feature you wanted and behaves like a foot-gun in practice, because the
selector semantics changed across versions and a mistyped label can delete
unrelated objects. If you want prune semantics, use a GitOps controller that
computes the diff from a recorded inventory. If you must use it, run it with
--dry-run=server first and read every line of output.
Debugging a running workload
kubectl logs deploy/checkout --all-containers --since=15m
kubectl logs pod/checkout-7d8f9 -c sidecar --previous
kubectl exec -it pod/checkout-7d8f9 -c app -- sh
kubectl port-forward svc/checkout 8080:80
kubectl cp checkout-7d8f9:/tmp/heap.prof ./heap.prof
--previous reads the logs of the last terminated container, which is the only
way to see why a CrashLoopBackOff pod died. If the image is distroless and has
no shell, kubectl exec is useless and kubectl debug is the answer: it
attaches an ephemeral container that shares the pod’s namespaces.
# Attach a debug container to a running pod
kubectl debug -it pod/checkout-7d8f9 --image=busybox:1.36 --target=app
# Copy a pod and change its command, leaving the original untouched
kubectl debug pod/checkout-7d8f9 --copy-to=checkout-debug --set-image='*=busybox:1.36' -- sleep 1d
# Get a root shell on a node
kubectl debug node/ip-10-0-1-23 -it --image=busybox:1.36
The debug-running-pod task walks through all three modes and their permission requirements. Node debugging is effectively node root access, so it should be an audited, break-glass operation rather than a habit.
Ephemeral containers have two properties that surprise people. They cannot be
removed once added — the pod carries them until it is replaced — and they cannot
declare resource requests, so they run inside whatever headroom the pod’s
existing limits leave. On a pod that is already at its memory limit, attaching a
debug container can be the thing that triggers the OOM kill you were
investigating. Prefer --copy-to on production pods that are still serving
traffic, and reserve in-place debugging for pods that are already broken.
For resource questions, kubectl top needs metrics-server installed — it reads
the Metrics API, not the API server’s object store, as described in the
resource usage monitoring docs.
If kubectl top returns an error about the metrics API, the cluster is missing
an addon, not misconfigured.
Node operations that will not page someone
kubectl cordon ip-10-0-1-23
kubectl drain ip-10-0-1-23 --ignore-daemonsets --delete-emptydir-data --timeout=300s
kubectl uncordon ip-10-0-1-23
drain respects PodDisruptionBudgets, which means it can block indefinitely,
which is the correct behavior — a drain that ignores PDBs is an outage. The
safely drain a node
task and the disruptions concept page
explain the interaction. Always set --timeout so an automation loop fails
loudly instead of hanging, and check for single-replica Deployments with a
minAvailable: 1 PDB before you start, because that combination can never be
satisfied.
Permissions and identity
kubectl auth whoami
kubectl auth can-i create deployments --namespace payments
kubectl auth can-i --list --namespace payments
kubectl auth can-i delete pods --as=system:serviceaccount:payments:runner
kubectl auth whoami prints the identity the API server actually attributes to
your credentials, which resolves most “why is this forbidden” questions in one
command — see the
auth whoami reference.
--as impersonation is the fastest way to test a ServiceAccount’s RBAC before
shipping a workload that depends on it.
Making kubectl faster to type
Set defaults in kuberc rather than aliasing every command, keep kubectl and
k both working, and install plugins with
krew instead of copying binaries into
/usr/local/bin. Two conventions from the
kubectl conventions page
are worth adopting: prefer --dry-run=server over --dry-run=client when you
want real admission-controller feedback, and never rely on the default
namespace in scripts.
Shell completion is the other high-value setup step, because it turns resource
names and namespaces into tab-completable values and removes an entire class of
typo. It is installed per shell with kubectl completion bash or
kubectl completion zsh, documented alongside the
install instructions.
Pair it with a prompt that prints the current context and namespace. The cost of
a wrong-cluster delete is high enough that a permanently visible context
string pays for itself the first week.
The habit that saves the most time is not an alias. It is running
kubectl diff before every kubectl apply, on every cluster, every time. It
costs two seconds and it catches the class of mistake that no amount of YAML
review does.
Frequently asked questions
Is it safe to use a newer kubectl against an older cluster?+
Why does kubectl get pods not show the field I filter on?+
What is the difference between kubectl apply and kubectl apply --server-side?+
How do I debug a pod whose image has no shell?+
kubectl debug with an ephemeral container: kubectl debug -it pod/name --image=busybox --target=app. It joins the running pod's namespaces without restarting it. See the debug-running-pod task.Why does kubectl drain hang?+
Read next
- The YAML + JSON config reference
- Cron syntax, actually explained
- Try the tool: Kubernetes YAML linter
Tags: #kubernetes, #kubectl, #cli, #operations