Skip to main content

Common kubectl commands every developer should know

· 8 min read
Maintainer, NativeHub

There are hundreds of kubectl subcommands and you will use about fifteen. This is that fifteen, with the flags that turn each one from "prints something" into "answers the question."

The mental model

kubectl is a REST client. Every command is an HTTP request to the API server, and you can see exactly which one:

kubectl get pods -v=6 # show the request URLs
kubectl get pods -v=8 # show full request and response bodies

That is genuinely useful when a command behaves unexpectedly — you can see whether the problem is your query or the cluster's answer.

get — what exists

kubectl get pods
kubectl get pods -A # every namespace
kubectl get pods -o wide # + node, pod IP, nominated node
kubectl get pod web-abc -o yaml # the full object as stored
kubectl get deploy,svc,ing -l app=web # several kinds at once

The flags worth memorising

# Watch changes as they happen
kubectl get pods -w

# Filter server-side by field (cheaper than grep, and exact)
kubectl get pods --field-selector=status.phase=Running
kubectl get events --field-selector type=Warning

# Sort by anything in the object
kubectl get pods --sort-by=.status.containerStatuses[0].restartCount
kubectl get events --sort-by=.lastTimestamp

# Pull out exactly the columns you want
kubectl get pods -o custom-columns=\
'NAME:.metadata.name,NODE:.spec.nodeName,IMAGE:.spec.containers[*].image'

# JSONPath for scripting
kubectl get pods -o jsonpath='{.items[*].spec.nodeName}'
kubectl get svc web -o jsonpath='{.spec.clusterIP}'

kubectl api-resources is the command that makes the rest discoverable — it lists every kind the cluster knows about, along with its short name and whether it is namespaced.

kubectl api-resources | head -20
kubectl explain deployment.spec.strategy.rollingUpdate # inline schema docs

describe — why it is in that state

kubectl describe pod web-abc
kubectl describe node worker-1
kubectl describe svc web

describe is not just a prettier get. It joins the object with related state — most importantly its Events, which appear at the bottom and are where the actual explanation lives:

Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Failed 2m kubelet Error: ImagePullBackOff
Warning Unhealthy 1m kubelet Readiness probe failed: connection refused

Always read the bottom of describe first. It answers "why" more often than the logs do.

# Two high-value describe habits
kubectl describe pod web-abc | sed -n '/Events:/,$p' # events only
kubectl describe node worker-1 | sed -n '/Allocated/,$p' # what is claimed on a node

logs — what the application said

kubectl logs web-abc
kubectl logs web-abc -f # follow
kubectl logs web-abc --tail=100
kubectl logs web-abc --since=15m
kubectl logs web-abc -c sidecar # a specific container in the Pod

The two flags people miss

# The container that CRASHED, not the one that just started
kubectl logs web-abc --previous

# Every Pod behind a label, with the pod name prefixed on each line
kubectl logs -l app=web --all-containers --prefix --tail=50

--previous is the single most important debugging flag in kubectl. On a CrashLoopBackOff Pod, plain kubectl logs shows a container that has been alive for two seconds and has nothing to say.

# Timestamps are not on by default
kubectl logs web-abc --timestamps --since-time='2026-05-18T10:00:00Z'

exec — get inside

kubectl exec web-abc -- env | sort
kubectl exec -it web-abc -- sh
kubectl exec -it web-abc -c sidecar -- bash

The -- matters: everything after it is passed to the container rather than parsed by kubectl. Without it, kubectl exec web-abc ls -la tries to interpret -la as a kubectl flag.

# Useful one-liners from inside a Pod
kubectl exec web-abc -- cat /etc/resolv.conf
kubectl exec web-abc -- nslookup db.default.svc.cluster.local
kubectl exec web-abc -- wget -qO- --timeout=2 localhost:8080/healthz

When the image has no shell — distroless, scratch — exec cannot help. Use an ephemeral debug container instead, which joins the running Pod's namespaces:

kubectl debug -it web-abc --image=nicolaka/netshoot --target=app

port-forward — reach it from your laptop

kubectl port-forward pod/web-abc 8080:8080
kubectl port-forward svc/web 8080:80 # picks one backing Pod
kubectl port-forward deploy/web 8080:8080

# Bind on all interfaces (careful on shared networks)
kubectl port-forward --address 0.0.0.0 svc/web 8080:80

This is a tunnel through the API server, so it works even when the Service is ClusterIP-only and the cluster is behind a bastion. It is the fastest way to reach a database or an admin UI you would never expose.

Note that forwarding to a Service still selects one Pod and stays with it — it is not load balanced. For testing balancing behaviour, use a NodePort or an Ingress.

apply — declare the desired state

kubectl apply -f deployment.yaml
kubectl apply -f k8s/ # a whole directory
kubectl apply -k overlays/prod # kustomize
kubectl apply -f https://example.com/manifest.yaml

Before you apply

# Validate against the server's schema without changing anything
kubectl apply -f deployment.yaml --dry-run=server

# See exactly what would change on the live object
kubectl diff -f deployment.yaml

kubectl diff is underused and excellent — it is git diff for the cluster, and it catches the "someone edited this by hand in production" case immediately.

apply vs create vs replace. create fails if the object exists. replace overwrites the whole object, dropping fields you did not send. apply performs a three-way merge between your manifest, the live object, and the last-applied configuration — which is why it is the only one safe to run repeatedly.

Rollouts

kubectl rollout status deployment/web --timeout=120s
kubectl rollout history deployment/web
kubectl rollout history deployment/web --revision=3
kubectl rollout undo deployment/web
kubectl rollout undo deployment/web --to-revision=2
kubectl rollout restart deployment/web # recreate every Pod, no spec change

rollout restart is the clean way to force a restart — it patches an annotation on the Pod template, which triggers a normal rolling update. Deleting Pods by hand skips the surge and availability guarantees.

Quick reference

The commands worth committing to memory

CommandWhat it does
kubectl get pods -o widePods with node and IP columns
kubectl get all -n <ns>Common resource kinds in a namespace
kubectl describe pod <pod>Full state plus the events that explain it
kubectl explain <kind>.<field>Schema documentation, offline
kubectl api-resourcesEvery kind the cluster knows, with short names
kubectl logs <pod> --previousLogs from the container that crashed
kubectl logs -l app=web --prefix -fFollow logs from every matching Pod
kubectl exec -it <pod> -- shInteractive shell in a container
kubectl debug -it <pod> --image=nicolaka/netshoot --target=<c>Ephemeral debug container sharing the Pod namespaces
kubectl get events --sort-by=.lastTimestampCluster events in chronological order
kubectl top pod --containersLive CPU and memory per container
kubectl apply -f <file>Create or update via three-way merge
kubectl diff -f <file>Show what applying would change
kubectl scale deploy/<name> --replicas=5Change replica count immediately
kubectl rollout restart deploy/<name>Roll every Pod without a spec change
kubectl rollout undo deploy/<name>Revert to the previous ReplicaSet
kubectl port-forward svc/<name> 8080:80Tunnel a Service port to localhost
kubectl cp <pod>:/path ./localCopy files out of a container
kubectl auth can-i <verb> <resource>Check your own RBAC permissions
kubectl config get-contextsList configured clusters
kubectl config use-context <name>Switch cluster
kubectl config set-context --current --namespace=<ns>Set the default namespace

Make it faster

~/.bashrc or ~/.zshrc
# Completion and a short alias that keeps completion working
source <(kubectl completion bash)
alias k=kubectl
complete -o default -F __start_kubectl k

# Stop typing -n every time
alias kn='kubectl config set-context --current --namespace'

Two tools that pay for themselves in a week:

  • kubectx / kubens — switch cluster and namespace in one word.
  • k9s — a terminal UI over the cluster. Logs, exec, describe, and delete without typing a command; it replaces most of the get/describe loop.

The habits that matter

  1. Read the Events at the bottom of describe before anything else.
  2. Use --previous on any Pod that has restarted.
  3. Run kubectl diff before kubectl apply on a cluster you care about.
  4. Use --dry-run=server to validate manifests in CI.
  5. Set the namespace in your context instead of typing -n two hundred times a day.

Further reading