kubectl Command: Tutorial & Examples
Your remote control for a Kubernetes cluster — one verb, one object, one line.
What It Is
kubectl is how you talk to a Kubernetes cluster from the command line. You type a verb and a noun — kubectl get pods, kubectl delete deployment web — and a fleet of machines somewhere does your bidding. It's the steering wheel for an orchestra of containers: you describe what you want running, and the cluster spends the rest of its life making reality match. Anyone who runs anything on Kubernetes lives inside this command all day, every day.
If you've shipped containers with docker but never driven a cluster, this is the leap. And here's the kind thing: it's a smaller leap than it looks. kubectl has a grammar so regular that once you learn it for one kind of thing, you know it for all of them. We'll teach you that grammar from zero, explain every verb and the flags worth knowing, and — the part most tutorials never tell you — show you what kubectl actually is under the hood. (Spoiler: it's a web browser in a trench coat. We'll get there.) By the end this is both your first lesson and the reference you come back to for the flag you can never remember.
Note
kubectlis not installed by default on a server — it's a client you put on your laptop or a jump box, and it talks to a cluster over the network. You install it once (apt-get install kubectl, or grab the single static binary from the Kubernetes release page) and point it at a cluster with a config file. The cluster is the heavy thing;kubectlis just the remote.
How It Works: It's Just a REST Client
This is the one idea that turns Kubernetes from sorcery into something you can reason about, so let's nail it before anything else.
A Kubernetes cluster has a brain called the API server — a single HTTPS endpoint that holds the entire desired state of the world: every deployment, every pod, every service, every secret, stored as objects in a database (etcd). Everything else in the cluster — the schedulers, the controllers, the agents on each node — just watches that API server and scrambles to make the real world match what it says.
And kubectl? kubectl is just a REST client for that API server. That's the whole secret. When you run kubectl get pods, it makes an HTTP GET to /api/v1/namespaces/default/pods and pretty-prints the JSON that comes back. kubectl delete is an HTTP DELETE. kubectl apply is essentially a PATCH. The fancy command-line tool is a thin, friendly skin over plain web requests — you could do every single thing it does with curl and enough patience. Don't believe me? Every command takes --v=8, which prints the exact HTTP requests it's making:
kubectl get pods --v=8
GET https://10.0.0.1:6443/api/v1/namespaces/default/pods?limit=500
Request Headers:
Accept: application/json
User-Agent: kubectl/v1.29.0
Response Status: 200 OK
There it is — a GET, a URL, a 200 OK. The first time you see that, the whole system stops being a black box. Kubernetes isn't magic; it's a database with a REST API and a swarm of little robots reading from it. kubectl is how you join the swarm.
The second half of the idea: everything in Kubernetes is an object, and they all behave the same way. A pod is an object. A deployment is an object. A service, a secret, a namespace, a node — all objects, all stored the same way, all manipulated by the same handful of verbs. This is why the grammar is so regular: kubectl <verb> <kind> <name> works for every kind of thing. Learn get, describe, delete, and apply once, and you can drive resources you've never heard of. That uniformity is the deepest design decision in Kubernetes, and it's the reason a tool with maybe eight verbs you'll ever use can run a thousand-machine fleet.
Your First Look
Three commands tell you where you are and what's running. First, which cluster am I even pointed at? — the question that has saved a thousand careers:
kubectl config current-context
prod-eu-west
Then the bread-and-butter command, get, which lists objects of a kind:
kubectl get pods
NAME READY STATUS RESTARTS AGE
web-7d9fbc8f6c-2xk4p 1/1 Running 0 3d
web-7d9fbc8f6c-9vlqz 1/1 Running 0 3d
api-5c8d7f9b4d-pjr8t 1/1 Running 2 (4h ago) 6d
redis-0 1/1 Running 0 12d
Four columns of instant truth. READY is containers-ready / containers-total — 1/1 is healthy, 0/1 is a pod that started but isn't passing its health check yet. STATUS is the headline (Running, Pending, CrashLoopBackOff — more on that nightmare below). RESTARTS is how many times the container died and came back; 0 is calm, 2 (4h ago) means it stumbled but recovered, and a number climbing in real time is a fire. AGE is how long the object has existed.
When you don't know what's where, ask for everything across all namespaces:
kubectl get all -A
-A (short for --all-namespaces) is the "show me the whole building, not just my floor" flag, and all is a convenient pseudo-kind that bundles the common resource types. You'll type this one a lot.
How I Use It
When something's wrong, here's the exact sequence in my head — and it's almost always the same three commands, in the same order.
First, get to find the sick object. kubectl get pods and I scan the STATUS and RESTARTS columns. A Running 0/1 or a CrashLoopBackOff or a RESTARTS count ticking upward — that's my patient. The list is the triage.
Second, describe to get the story. kubectl describe pod web-7d9fbc8f6c-2xk4p dumps everything Kubernetes knows about that object, and — crucially — the Events at the bottom: a timestamped log of what the cluster tried to do and what went wrong. "Failed to pull image." "Insufficient memory." "Liveness probe failed." Nine times out of ten the answer is sitting in those Events, in plain English, and people miss it because they never scroll down. describe is the command that explains why a thing is the way it is.
Third, logs to hear it from the app itself. If the object is fine but the program inside is misbehaving, kubectl logs web-7d9fbc8f6c-2xk4p shows me its stdout — the application's own side of the story. Add -f to follow it live, --previous to read the logs of the container before it crashed (the single most useful flag for a CrashLoopBackOff, because the evidence died with the old container).
That's the craft: get to find it, describe to learn why, logs to hear it speak. Then, if I need to be inside the container poking around, kubectl exec -it <pod> -- bash drops me into a shell on it, exactly like ssh but tunneled through the API server. Four commands cover ninety percent of every bad day.
The Verbs, Explained
Every kubectl command is kubectl <verb> <kind> [name] [flags]. Here's the full set of verbs worth knowing, grouped by what they're for.
Looking (safe — these only read):
get— list objects of a kind, one line each. The default glance. Add-o widefor more columns,-o yamlfor the full object,-o jsonto pipe intojq.describe— the human-readable deep dump of one object, including the Events at the bottom. Your "why is this broken" command.logs— the stdout/stderr of the program inside a pod.-ffollows,--previousreads the dead container's last words,--tail=100limits it.top— live CPU and memory for pods or nodes (kubectl top pods), the cluster's answer totop. Needs the metrics-server add-on installed.explain— the built-in manual:kubectl explain pod.spec.containersdocuments every field of every object, straight from the cluster's own schema. Wildly underused.
Changing (these mutate — think before you press Enter):
apply— the big one. Send a YAML file describing desired state; the cluster reconciles to it. This is the right way to change anything (see below).create— make an object that doesn't exist yet. Unlikeapply, it errors if it's already there.delete— remove an object.kubectl delete pod x— but on a managed pod the controller just makes a new one, which surprises everyone once.edit— open an object's live YAML in$EDITOR; save and it's applied. Handy, but changes made this way aren't in your files — a trap we'll name in Gotchas.scale—kubectl scale deployment web --replicas=5, the fast way to add or remove copies.rollout— manage deployments:rollout statuswatches a deploy land,rollout undorewinds to the previous version (yes, one command to roll back — keep that one close),rollout restartcycles every pod.
Connecting (reach into the cluster from your laptop):
exec— run a command inside a container.kubectl exec -it web-xxx -- bashfor an interactive shell.port-forward— tunnel a cluster-internal port to your local machine:kubectl port-forward svc/redis 6379:6379, and a service that only listens inside the cluster now answers on your laptop'slocalhost. The encrypted-pipe trick, Kubernetes edition.cp— copy files in or out of a pod, likescpfor containers.
Knowing where you are (run these constantly):
config— manage which cluster you're pointed at.config current-context(where am I?),config use-context prod(switch),config get-contexts(list them all).cluster-info,api-resources(list every kind of object this cluster knows — your map of the territory),version.
Declarative: The Magic Most People Miss
Here's the idea that, once it clicks, makes Kubernetes feel less like a war and more like a thermostat.
Most tools are imperative: you tell them the steps. "Start a container. Now start another. Now wire them together." If a step fails, or a container dies at 3am, the steps don't re-run themselves — the world drifts from what you wanted, and you get paged. This is how docker run works, and it's fine for one box.
Kubernetes is declarative: you describe the destination, not the route. You write a YAML file that says "I want three copies of this image running, always," hand it over with apply, and then you walk away. You never told the cluster how to get there. You told it where "there" is, and a swarm of controllers spends the rest of eternity closing the gap between what-is and what-you-asked-for. This loop has a name worth knowing — reconciliation — and it's the soul of the whole system:
kubectl apply -f web-deployment.yaml
deployment.apps/web configured
Now the genuinely cool part — go ahead and break things on purpose. Delete one of the pods:
kubectl delete pod web-7d9fbc8f6c-2xk4p
pod "web-7d9fbc8f6c-2xk4p" deleted
…and watch a new one materialize before you can blink:
kubectl get pods
NAME READY STATUS AGE
web-7d9fbc8f6c-9vlqz 1/1 Running 3d
web-7d9fbc8f6c-7bx2m 0/1 ContainerCreating 2s
You said "three, always." You now have two. The cluster noticed within milliseconds and is already building the third — nobody woke up, nobody ran a script, nobody got paged. That's reconciliation: the world is constantly nudged back toward the YAML you wrote. Kill a node, and every pod it was running gets reborn elsewhere. Set replicas to 50 and the cluster sprints to fifty. You stopped giving orders and started describing a destination, and the machine became a thermostat for your infrastructure. The first time you delete a pod and watch its replacement appear unbidden, you get it — and you don't go back.
This is why apply -f (declarative) beats create/delete/scale typed by hand (imperative) for anything real: your YAML files become the single source of truth, you keep them in git, and the cluster's job is simply to make the world look like git. People who run Kubernetes well barely type mutating commands at all — they edit a file and apply it. The cluster does the rest, forever.
Reading It by Example
Build instinct fast — here are the STATUS values you'll actually meet and what each one is trying to tell you.
Running, READY 1/1 — healthy. The container is up and passing its health checks. This is the boring word you want to see.
Running, READY 0/1 — the container started but is failing its readiness probe. The process is alive but the app inside isn't answering "yes, I'm ready" — still booting, can't reach its database, wrong port configured. The pod won't receive traffic until that flips to 1/1. describe and logs it.
Pending — Kubernetes accepted the pod but can't place it anywhere. Almost always: no node has enough free CPU or memory, or you asked for a resource that doesn't exist. describe the pod; the Events will say Insufficient memory or 0/3 nodes are available in plain words.
ContainerCreating — being born right now: pulling the image, mounting volumes, wiring the network. Fine for a few seconds; stuck here for minutes usually means a slow or failing image pull, or a volume that won't mount.
ImagePullBackOff / ErrImagePull — couldn't fetch the container image. Wrong image name, wrong tag, or a private registry it can't authenticate to. The BackOff means it's retrying with ever-longer waits, which is why it's not hammering the registry. Check your image name first; it's a typo more often than you'd admit.
CrashLoopBackOff — the famous one, and the source of countless lost evenings. The container starts, crashes, restarts, crashes again, and Kubernetes is backing off — waiting longer between each restart so it doesn't spin the CPU. The pod isn't broken; the program inside it is exiting. The fix is never in Kubernetes — it's in kubectl logs --previous <pod>, which shows you the dying container's last words. (Read that flag again: --previous. Without it you get the logs of the fresh container, which has barely started and tells you nothing. The evidence is always in the corpse, not the newborn.)
Terminating stuck for ages — the pod was told to die but a finalizer or a hung process is holding it. Usually patience; occasionally --grace-period=0 --force, which is the Kubernetes equivalent of kill -9 and just as blunt.
Cheat Sheet
The moves worth muscle memory:
kubectl get pods·get pods -Aall namespaces ·get pods -o widemore columns ·get pods -wwatch livekubectl describe pod <name>— the why, with Events at the bottomkubectl logs <pod>·-ffollow ·--previousthe crashed container's last words ·--tail=100kubectl exec -it <pod> -- bash— a shell inside the containerkubectl apply -f file.yaml— the right way to change anything ·-k dir/for kustomizekubectl delete -f file.yaml— undo an apply cleanlykubectl scale deployment web --replicas=5·kubectl rollout undo deployment web— instant rollbackkubectl port-forward svc/redis 6379:6379— tunnel a cluster port to localhostkubectl top pods/top nodes— live CPU + memorykubectl explain pod.spec— the built-in field-by-field manualkubectl config use-context prod— switch clusters ·current-context— which one am I on?!
Three flags that work on almost everything: -n <namespace> (scope to a namespace), -o yaml|json|wide (output format), --dry-run=client (show what would happen without doing it — your safety net).
Pro Tip
kubectl get pod web-xxx -o yamldumps the object's complete live spec — every field the API server knows, defaults filled in and all. Pipe it tojq(after-o json) to extract one value, or diff two of them to see exactly what changed between two deployments. Whendescribeisn't detailed enough,-o yamlis the ground truth.
Gotchas
- The wrong-cluster catastrophe.
kubectlquietly remembers which cluster you're pointed at (the context). Run adeletethinking you're on staging when you're on prod and you've ruined an afternoon — or a quarter. Checkkubectl config current-contextbefore anything destructive. Many people put the context name right in their shell prompt for exactly this reason; it's worth doing on day one. kubectl editandkubectl scalecreate drift. Change a live object by hand and your YAML files in git no longer match reality — the nextapplyfrom a teammate silently reverts you, or worse, doesn't. Treat hand-edits as emergency-only; the real change belongs in the file.- Deleting a managed pod does nothing lasting.
kubectl delete pod xon a pod owned by a deployment just triggers a replacement — that's reconciliation doing its job. To actually stop something, scale its deployment to zero or delete the deployment, not the pod. kubectlversion vs cluster version. The client and the cluster can drift apart; Kubernetes only promises compatibility within one minor version each way. A weird error on an old laptop is sometimes just a stalekubectl.- YAML's whitespace will bite you. Kubernetes objects are YAML, and YAML cares about indentation with the intensity of a Python interpreter. A
applythat fails with "error converting YAML" is nearly always a stray space.kubectl apply --dry-run=client -f file.yamlcatches it before the cluster does.
History & Philosophy
Kubernetes came out of Google in 2014, the open-source grandchild of an internal system called Borg that had been scheduling Google's containers for a decade before anyone outside had heard the word. The name is Greek for helmsman — the person steering the ship — which is why the logo is a ship's wheel and why the community shortens it to K8s (a "numeronym": K, then eight letters, then s; the same trick gives us i18n for internationalization and a11y for accessibility). kubectl is, fittingly, "kube control" — you, with your hands on the helmsman's wheel.
And there's a small, lovely fight worth knowing about, because you'll hear it in the wild: how do you pronounce it? "cube-control," "cube-cuddle," "cube-C-T-L," "cube-cuttle" — the project's own contributors don't agree, and a Kubernetes maintainer eventually had to publish the "official" answer ("cube-control") just to end the bar arguments. There is no wrong one. Say it with confidence and nobody will fight you.
But the idea worth carrying out of here is the reconciliation loop. Borg's great insight — the one Kubernetes inherited and made the world's — was that you should never script your infrastructure imperatively, because scripts can't heal. Instead you declare the desired state and build tireless little robots whose only job is to notice drift and close it. That pattern outgrew Kubernetes entirely; it's now how Terraform manages clouds, how GitOps tools like ArgoCD keep clusters in sync with git, how modern infrastructure thinks. Pull on the thread of kubectl apply and you unravel a whole philosophy: stop telling computers how, start telling them what, and let a control loop do the rest. The thermostat on your wall has been doing it for a century. Kubernetes just did it for a datacenter.
See Also
- Kubernetes — the cluster itself, what
kubectldrives docker— build and run the containers Kubernetes orchestratescurl— whatkubectlsecretly is, underneathjq— slice the JSON that-o jsongives yousystemctl— the single-machine cousin of "keep this thing running"- container — the unit of everything here
- pod — Kubernetes' smallest deployable object
- DevOps — the culture this whole stack grew out of
Pods crashing, statuses you can't decode, and no idea which cluster you're even pointed at?
CleverUptime watches the boxes your cluster runs on — the CPU, memory, and disk pressure that turn healthy pods into Pending and OOM-killed ones — and tells you in plain language which node is about to tip over, before the reconciliation loop runs out of room to heal.
Want to see your own server's health right now? One command, no signup, no install.