kubectl talks to the Kubernetes API, not to your cluster directly. Every command follows kubectl VERB TYPE NAME, and most of what trips people up (wrong context, a deleted Pod that comes right back, logs that show nothing) comes from not knowing what's actually managing what. This reference is organized by task, not alphabetically: context, reading resources, debugging, creating things the right way, rollouts, and the five traps that waste an afternoon.
🧭 The kubectl Grammar
Almost every command is the same shape.
kubectl verb type name flags
kubectl get pod my-app -o wide is verb get, type pod, name my-app, flag -o wide. Once this clicks, you stop memorizing commands and start assembling them.
| Verb | Does |
|---|---|
get | Lists or reads current state |
describe | Reads state plus recent events, human formatted |
create | Makes a new object imperatively, errors if it exists |
apply | Creates or updates from a file, declarative |
delete | Removes an object |
edit | Opens the live object in your editor |
logs | Reads container stdout/stderr |
exec | Runs a command inside a container |
rollout | Manages Deployment history |
port-forward | Tunnels a local port to a Pod or Service |
When you forget a command, don't reach for a search engine. Say the sentence out loud: "I want to VERB a TYPE named NAME." It's usually already valid kubectl.

Drive Your First Cluster with kubectl Verbs | CloudLearn
Practice kubectl get, describe, and explain on a pre-built kind cluster before touching any YAML.
🔌 Context and Cluster Access
kubeconfig can hold several clusters at once. Context decides which one you're talking to, and namespace decides which slice of it.
| Command | What It Does |
|---|---|
kubectl config get-contexts | Lists every context in your kubeconfig |
kubectl config current-context | Shows which one is active right now |
kubectl config use-context <name> | Switches clusters |
kubectl config set-context --current --namespace=<ns> | Pins the default namespace for the current context |
kubectl config view --minify | Shows only the active context's config, not the whole file |
use-context switches the cluster. It does not switch the namespace. You can be pointed at the right cluster and the wrong namespace and every get will look empty or wrong, with no error telling you why.
🔍 Get vs Describe
Two different questions. get answers "what exists and what state is it in." describe answers "what happened to it recently."
| Command | What It Does |
|---|---|
kubectl get pods | Lists Pods in the current namespace |
kubectl get pods -A | Lists Pods across every namespace |
kubectl get pods -o wide | Adds node, IP, and readiness columns |
kubectl get pods -w | Watches for changes live |
kubectl get pod <name> -o yaml | Full object definition as YAML |
kubectl describe pod <name> | State plus the Events section at the bottom, which is where most answers live |
Read describe's Events section before anything else when a Pod misbehaves. That's where ImagePullBackOff, failed scheduling, and readiness probe failures actually show up. get will just say Pending or CrashLoopBackOff with no reason attached.
🐛 Debugging
| Command | What It Does |
|---|---|
kubectl logs <pod> | Current container's stdout/stderr |
kubectl logs <pod> -f | Streams logs live |
kubectl logs <pod> -c <container> | Picks a container in a multi-container Pod |
kubectl logs <pod> --previous | Logs from the container's last run, before it crashed |
kubectl get events --sort-by=.lastTimestamp | Cluster-wide event stream, newest last |
kubectl top pod / kubectl top node | Live CPU and memory, needs metrics-server |
kubectl debug <pod> -it --image=busybox --target=<container> | Attaches an ephemeral debug container to a running Pod |
--previous only works while the crashed container's record still exists. Once the Pod restarts again or gets rescheduled, that log is gone. Grab it on the first crash, not the third.
kubectl exec -it <pod> -- sh fails with "executable file not found" on distroless or scratch images, because there's no shell to exec into. That's not a permissions problem. kubectl debug with an ephemeral container is the only way in.

Inspect, Create, and Debug Your First Pod with kubectl | CloudLearn
Author a Pod, watch its lifecycle, debug an ImagePullBackOff with describe, then prove a multi-container Pod shares localhost networking and an emptyDir volume.
🏗️ Imperative vs Declarative
Two ways to create things, and they don't mix cleanly.
| Command | What It Does |
|---|---|
kubectl run nginx --image=nginx | Imperative: creates one Pod right now |
kubectl create deployment nginx --image=nginx | Imperative: creates a Deployment right now |
kubectl apply -f deployment.yaml | Declarative: makes the cluster match the file, creating or updating as needed |
kubectl delete -f deployment.yaml | Removes everything defined in that file |
apply tracks what it last applied in an annotation, so it can compute a clean diff next time. An imperative edit, whether that's kubectl edit, kubectl scale, or kubectl set image, changes the live object without updating that annotation. The next apply from the original file can silently revert your imperative change, because as far as apply knows, nothing changed on its end. Pick one method per object and stay there.
📝 Generating YAML Without Writing It
The fastest way to get a correct starting YAML file is to have kubectl generate one and edit from there.
| Command | What It Does |
|---|---|
kubectl create deployment nginx --image=nginx --dry-run=client -o yaml | Prints the YAML, creates nothing |
kubectl run nginx --image=nginx --dry-run=client -o yaml > pod.yaml | Same idea, saved to a file |
kubectl create service clusterip my-svc --tcp=80:8080 --dry-run=client -o yaml | Generates a Service definition |
kubectl expose deployment nginx --port=80 --dry-run=client -o yaml | Generates a Service from an existing Deployment's labels |
--dry-run=client never touches the cluster. --dry-run=server sends the request to the API server for validation and admission checks, then discards it, which catches errors the client-side check can't see, at the cost of one real API round trip.
🔄 Rollouts and Rollback
Deployments keep a revision history. This is how you use it.
| Command | What It Does |
|---|---|
kubectl rollout status deployment/<name> | Watches a rollout until it finishes or stalls |
kubectl rollout history deployment/<name> | Lists past revisions |
kubectl rollout undo deployment/<name> | Rolls back to the previous revision |
kubectl rollout undo deployment/<name> --to-revision=3 | Rolls back to a specific one |
kubectl rollout restart deployment/<name> | Forces new Pods without changing the image, useful after a ConfigMap or Secret update |
kubectl set image deployment/<name> <container>=<image> | Updates the image and triggers a rollout |
rollout restart is the fix when Pods need to pick up a changed ConfigMap or Secret, since those don't trigger a rollout on their own unless the Pod spec references them by hash or you're using a tool that does.
🔗 Port Forward
Talk to something inside the cluster without exposing it.
| Command | What It Does |
|---|---|
kubectl port-forward pod/<name> 8080:80 | Local port 8080 to that Pod's port 80 |
kubectl port-forward svc/<name> 8080:80 | Same, but through a Service, so it survives the Pod restarting |
kubectl port-forward deployment/<name> 8080:80 | Picks one Pod from the Deployment automatically |
Forward to the Service, not the Pod, for anything you'll run for more than a minute. A Pod-level forward dies the instant that specific Pod is replaced.
📦 Namespaces and Cleanup
| Command | What It Does |
|---|---|
kubectl create namespace <name> | Makes a new namespace |
kubectl get all -n <namespace> | Lists the common object types in one namespace |
kubectl delete namespace <name> | Deletes the namespace and everything in it |
kubectl delete pods --field-selector=status.phase=Failed | Cleans up only failed Pods |
kubectl delete pods --all -n <namespace> | Deletes every Pod in a namespace, Deployments will recreate theirs |
kubectl delete namespace is not reversible and does not ask twice. Everything in it, including PersistentVolumeClaims, goes with it.
🔎 JSONPath and Custom Columns
For when -o wide isn't the column you need.
| Command | What It Does |
|---|---|
kubectl get pods -o jsonpath='{.items[*].metadata.name}' | Just the Pod names, space separated |
kubectl get pods -o custom-columns=NAME:.metadata.name,STATUS:.status.phase | A table with exactly the columns you asked for |
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.capacity.cpu}{"\n"}{end}' | Node name and CPU capacity, one per line |
Scripting, or feeding kubectl output into another command. For a human reading the terminal, -o wide or describe is almost always faster to parse.
🔐 Permissions: auth can-i
Before assuming an error is a bug, check whether it's RBAC.
| Command | What It Does |
|---|---|
kubectl auth can-i create pods | Can you, right now, create Pods in this namespace |
kubectl auth can-i list secrets -n kube-system | Checks a specific namespace |
kubectl auth can-i create pods --as=system:serviceaccount:default:my-sa | Checks what a ServiceAccount can do, not you |
kubectl auth can-i '*' '*' | Are you effectively cluster-admin |
--as is the fastest way to debug "works for me, fails in the Pod." Run the same check as the ServiceAccount the Pod actually uses, instead of guessing at its RoleBindings.
⚠️ Five Traps
| Trap | What Actually Happens |
|---|---|
| Deleting a Pod that belongs to a Deployment | The Pod comes right back. You deleted an instance, not the thing managing it. Delete the Deployment, or scale it to 0, if that's the actual goal. |
Editing an object that apply also manages | The next apply can silently undo your edit, because apply's diff is against its own last-applied record, not against what's currently live. |
exec-ing into a distroless image | No shell exists to exec into. Looks like a permissions error, isn't one. Use kubectl debug instead. |
Trusting get alone when a Pod won't start | get shows the status word. describe's Events section shows the reason. Always check both. |
| Assuming a context switch also switches namespace | It doesn't. You can run a command against the right cluster, wrong namespace, and get a plausible-looking empty result instead of an error. |
⚡ Ten to Actually Remember
kubectl get pods -A
kubectl describe pod <name>
kubectl logs <pod> -f --previous
kubectl exec -it <pod> -- sh
kubectl apply -f .
kubectl rollout status deployment/<name>
kubectl rollout undo deployment/<name>
kubectl port-forward svc/<name> 8080:80
kubectl config use-context <name>
kubectl auth can-i <verb> <resource>📦 Past kubectl: Helm
Once raw manifests start repeating across environments, the next tool most teams reach for is Helm, Kubernetes' package manager. It packages a set of manifests into one versioned, reusable chart you install, upgrade, and roll back as a unit instead of hand-maintaining YAML per environment.

Install Your First Helm Chart on Kubernetes | CloudLearn
Use Helm as a package manager - add a repo, install a sample app chart, override values, upgrade, roll back, and uninstall.