Kubectl commands: A comprehensive guide
A practical guide covering Kubectl shortcuts, autocomplete, context switching, selectors, output formats, troubleshooting, and advanced tips to boost your Kubernetes productivity.
Written by
Technical Writer at Civo
Written by
Technical Writer at Civo
This tutorial is for anyone looking to get more productive with kubectl, the Kubernetes command-line management tool. If you deal with Kubernetes clusters and their components, you will be doing it through kubectl. While this tutorial assumes you have kubectl already installed, I hope you find something useful here. I have tried to cover tricks for benefiting beginner kubectl usage, as well as more advanced tips.
Setting up kubectl autocomplete
The first thing I will always do when using kubectl is set autocomplete, which allows you to use tab auto-completion when composing commands. This is extremely useful; for example, if you are unsure of the exact name of a resource in a namespace, you can compose your command with -n namespace first, and then tab will present the resources in that particular namespace, which saves running a kubectl get to retrieve all resources first.
If bash:
source <(kubectl completion bash)echo "source <(kubectl completion bash)" >> ~/.bashrc
On Bash, kubectl completion depends on the bash-completion package being installed first. If completion does nothing after sourcing, install bash-completion from your package manager (for example apt install bash-completion or brew install bash-completion), then reopen your shell. Zsh has the completion machinery built in, so it needs no extra package.
For Zsh:
source <(kubectl completion zsh)echo "[[ \$commands[kubectl] ]] && source <(kubectl completion zsh)" >> ~/.zshrc
Once enabled, hit Tab to complete resource names inside a namespace instead of looking them up by hand.
kubectl configuration and contexts
A context bundles three things together: a cluster, a user (the credentials), and a default namespace. Switching context is how you jump from staging to production without retyping connection details every time.
Point kubectl at a specific kubeconfig file, either through the environment or per command:
KUBECONFIG=~/.kube/configkubectl get nodes --kubeconfig ~/custom_config
List the contexts you have, see which one is active, and switch between them:
kubectl config get-contextskubectl config current-contextkubectl config use-context production-contextkubectl config view
kubectl config get-contexts gives you the full picture at a glance:
CURRENT NAME CLUSTER AUTHINFO NAMESPACE* staging-context staging staging-user defaultproduction-context production production-user default
The asterisk marks the active context. After use-context, every subsequent command talks to that cluster with that user and that default namespace.
Working with namespaces
Namespaces partition a cluster into separate areas. Most commands default to the default namespace, so you will reach for these flags constantly.
Target one namespace, or span all of them at once:
kubectl get pods -n namespace-namekubectl get pods -A
If you are living in one namespace for a while, stop typing -n every time and pin it to your current context:
kubectl config set-context --current --namespace=namespace-name
That setting sticks per context, so staging and production can each remember their own default namespace.
Troubleshooting Kubernetes pods
Pending pods
When a pod is stuck in Pending, it has not been scheduled to a node yet. The reason is almost always in the events at the bottom of describe:
kubectl describe pod pod-name
Look for messages about insufficient CPU or memory, unsatisfied node selectors, or taints the pod cannot tolerate.
Failed pods and container logs
When a pod is running but misbehaving, the logs are your first stop. Pull logs from a pod, a specific container, or every container at once:
kubectl logs pod-name -n namespacekubectl logs pod-name -n namespace -c container1kubectl logs pod-name -n namespace --all-containers
Follow a live stream with -f to watch output as it happens:
kubectl logs pod-name -n namespace -c container1 -f
Limit to the recent past with a relative window:
kubectl logs pod-name -n namespace -c container1 --since=5m
And if the container has restarted, read the logs from the instance that died, not the fresh one:
kubectl logs pod-name -n namespace --previous
Time-bounded logs: pinning an incident window
The --since=5m flag is great for "what just happened," but incident reviews need absolute time. When you are reconstructing "10:00 to 10:15 last night," relative windows are useless. We will use --since-time with an RFC3339 timestamp, and add --timestamps so every line carries its own clock.
Start logs from an exact instant:
kubectl logs civo-pod -n civo-namespace --since-time="2026-06-05T10:00:00Z"
Print a timestamp on every line so you can see exactly when each event landed:
kubectl logs civo-pod -n civo-namespace --since-time="2026-06-05T10:00:00Z" --timestamps
Combine with --tail to cap how much you pull back, which is useful on chatty pods:
kubectl logs civo-pod -n civo-namespace --since-time="2026-06-05T10:00:00Z" --timestamps --tail=200
Grab the previous container instance with timestamps, handy right after a crash-loop restart:
kubectl logs civo-pod -n civo-namespace --previous --timestamps
Now the honest limitation: there is no --until. kubectl can set a start time but not an end time. So to isolate a window like 10:00 to 10:15, we set --since-time to the start, add --timestamps, and filter the tail of the window with standard text tools:
# logs between 10:00:00 and 10:15:00 (RFC3339 timestamps sort lexicographically)kubectl logs civo-pod -n civo-namespace --since-time="2026-06-05T10:00:00Z" --timestamps \| awk '$1 <= "2026-06-05T10:15:00Z"'
Or grep a single suspicious minute out of that stream:
kubectl logs civo-pod -n civo-namespace --since-time="2026-06-05T10:00:00Z" --timestamps \| grep "2026-06-05T10:12"
Two things to remember: only one of --since and --since-time may be used at once, and RFC3339 timestamps sort correctly as plain strings, which is exactly why the awk comparison above works. kubectl is blunt about both rules if you cross them:
kubectl logs ... --until=1m -> error: unknown flag: --untilkubectl logs ... --since --since-time -> error: at most one of `sinceTime` or `sinceSeconds` may be specified
Confirming there is no --until flag and that --since and --since-time are mutually exclusive.
Watching for changes
Instead of running get in a loop, ask kubectl to stream updates with -w:
kubectl get pods -w
When you need to block a script until a resource reaches a state, use wait:
kubectl wait --for=condition=Ready pod/civo-pod --timeout=30skubectl wait --for=condition=Ready=false pod/civo-podkubectl wait --for=delete pod/civo-pod
wait is the difference between a flaky sleep-and-hope script and one that proceeds the instant the cluster is actually ready.
Using labels and selectors
Labels are how you slice a cluster into the set of resources you care about. Selectors are how you query by those labels.
Match a single label:
kubectl get pods --selector=app=civo-app
Negate a label to exclude a set, for example every node that is not a control-plane node:
kubectl get node --selector='!node-role.kubernetes.io/master'
Field selectors filter on built-in fields rather than labels, and you can combine them:
kubectl get pods --field-selector status.phase=Runningkubectl get pods --field-selector=status.phase!=Running,spec.restartPolicy=Always
Sorting query results
Sort output to make patterns jump out, by name or by any field in the resource:
kubectl get services --sort-by=.metadata.namekubectl get pv --sort-by=.spec.capacity.storage
The --sort-by value is a JSONPath into the resource, so you can sort by almost anything the API exposes.
Monitoring resource usage
kubectl top shows live CPU and memory consumption for pods and nodes:
kubectl top podskubectl top nodeskubectl top pod civo-pod --containerskubectl top nodes --sort-by memory
One prerequisite catches people out: kubectl top needs metrics-server running in the cluster. Without it, you get error: Metrics API not available and no clue why. Check whether it is installed:
kubectl get deployment metrics-server -n kube-system
If that returns nothing, install metrics-server first. On a fresh cluster, even after installing it, kubectl top can briefly return error: metrics not available yet until metrics-server has collected its first sample, so give it a minute.
Working with output formats
YAML output
Dump the full resource definition to a file, useful for inspection or as a starting manifest:
kubectl get pod civo-pod -o yaml > civo-pod.yaml
JSON with jq
JSON output plus jq lets you ask precise questions. Find every pod that has restarted at least ten times across all namespaces:
kubectl get pods -A -o json | jq -r '.items[] | select(.status.containerStatuses[].restartCount >= 10) | .metadata.namespace + "/" + .metadata.name + " = " + (.status.containerStatuses[].restartCount | tostring)'
You can even generate other commands. This one finds paused KubeVirt VMs and prints the virtctl command to restart each:
kubectl get virtualmachineinstances.kubevirt.io -A -o json | jq '.items[] | select(.status.conditions[] | select(.type=="Paused" and .status =="True")) | {"namespace": .metadata.namespace, "name": .metadata.name}' -rc | jq '"virtctl restart " + .name + " -n " + .namespace' -rvirtctl restart virtual-machine-instance -n machine-namespace
Custom columns
When you want a tidy table of exactly the fields you care about, use custom-columns:
kubectl get pods --output=custom-columns="NAME:.metadata.name,IMAGE:.spec.containers[*].image"NAME IMAGEkube-apiserver-01 k8s.gcr.io/kube-apiserver:v1.21.9
Inspecting events
Events are the cluster's running commentary on what it is doing and why things fail. The bottom of describe shows events for a single object:
kubectl describe pod civo-pod
To see events across the namespace in time order, sort them:
kubectl get events --sort-by=.metadata.creationTimestamp
A quick note on the sort key, because most blog posts use a different one. You will often see --sort-by=.lastTimestamp. On modern clusters --sort-by=.metadata.creationTimestamp is the more reliable choice, because on the events.k8s.io/v1 API lastTimestamp is a legacy compatibility field (the modern equivalents are eventTime and series.lastObservedTime). Sorting by .metadata.creationTimestamp sidesteps the whole question, which is why this guide uses it.
Debugging controllers: walk the Deployment to ReplicaSet to Pod chain
When a rollout is stuck, the pod is often the last place the useful information shows up. A Deployment creates a ReplicaSet, the ReplicaSet creates Pods, and each layer emits its own events. We will walk the chain from the top.
First, ask the Deployment directly whether it is making progress:
kubectl rollout status deploy/civo-appkubectl rollout history deploy/civo-app
Read the controller-emitted events scoped to just this Deployment:
kubectl get events --field-selector involvedObject.kind=Deployment,involvedObject.name=civo-app
Drop to warnings only when the noise is high:
kubectl get events --field-selector type=Warning,involvedObject.kind=Deployment,involvedObject.name=civo-app
Sort events chronologically. --sort-by=.lastTimestamp is the classic form; --sort-by=.metadata.creationTimestamp is the more reliable key on modern clusters for the reason described just above:
kubectl get events --sort-by=.lastTimestampkubectl get events --sort-by=.metadata.creationTimestamp
Watch events live across all namespaces while you trigger the rollout in another terminal:
kubectl get events -A -w
Then describe each layer. The Deployment tells you which ReplicaSet it is scaling; the ReplicaSet tells you why it cannot create pods (quota, image pull, scheduling):
kubectl describe deployment/civo-appkubectl describe replicaset -l app=civo-app
To see the chain concretely, deliberately break a rollout by pointing it at an image tag that does not exist. The Warning events surface the failure right away:
Warning Failed pod/civo-app-59488f449b-xxxxx Error: ImagePullBackOffdeployment/civo-app ScalingReplicaSet Scaled up replica set civo-app-59488f449b to 1
Warning events surfacing the ImagePullBackOff after a deliberately broken rollout.
The mental model: if rollout status hangs, describe the Deployment to find the new ReplicaSet, describe that ReplicaSet to see whether pods are even being created, and only then describe a Pod. The stuck reason is usually one layer above where people start looking.
Executing commands in pods
Run a one-off command inside a running container:
kubectl exec civo-pod -- ls /
Or open an interactive shell with -i -t:
kubectl exec civo-pod -c bash-container -i -t -- bash
The -c flag picks the container when a pod has more than one.
Copying data to and from containers
kubectl cp moves files in either direction between your machine and a container:
kubectl cp /tmp/file.txt civo-namespace/civo-pod:/root/kubectl cp civo-namespace/civo-pod:/root/file.txt /tmp/
This is handy for pulling out a config dump or pushing in a test fixture without rebuilding an image.
The Oh My Zsh kubectl plugin
If you use Zsh with Oh My Zsh, its kubectl plugin gives you a set of short aliases for commonly used commands (for example k for kubectl, kgp for kubectl get pods). Enable it in your .zshrc plugin list and you shave a lot of typing off everyday work.
krew: the kubectl plugin manager
The Oh My Zsh plugin above gives you shell aliases. krew is a different thing: it is the official plugin manager from Kubernetes SIG CLI that installs new kubectl <verb> subcommands. This is the real extension ecosystem for kubectl.
Install krew with the official snippet:
(set -x; cd "$(mktemp -d)" &&OS="$(uname | tr '[:upper:]' '[:lower:]')" &&ARCH="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/\(arm\)\(64\)\?.*/\1\2/' -e 's/aarch64$/arm64/')" &&KREW="krew-${OS}_${ARCH}" &&curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/${KREW}.tar.gz" &&tar zxvf "${KREW}.tar.gz" &&./"${KREW}" install krew)
Then add krew to your PATH (put this in ~/.bashrc or ~/.zshrc):
export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"
Open a new shell, refresh the plugin index, and confirm it works:
kubectl krew updatekubectl krew searchkubectl krew list
Install a few high-value plugins:
kubectl krew install ctx ns stern view-secret neat tree
What each one buys you:
- ctx (
kubectl ctx): switch between clusters and contexts without typing the longkubectl config use-contextform. - ns (
kubectl ns): switch your default namespace in one word. - stern (
kubectl stern <query>): tail logs from many pods at once with color-coded, per-pod prefixes. This is the multi-pod answer tokubectl logs -f. - view-secret (
kubectl view-secret <secret>): print a Secret's values already base64-decoded, instead of piping throughbase64 -dby hand. - neat (
kubectl neat): strip the server-added clutter (managedFields,status, default annotations) out of-o yaml, leaving a clean manifest you can commit. - tree (
kubectl tree <kind> <name>): show the ownership tree of a resource, for example a Deployment down through its ReplicaSet to its Pods.
Two of them in action:
kubectl stern civo-app --since 5mkubectl tree deployment civo-app
kubectl tree makes the controller chain we walked earlier visible at a glance:
Deployment/civo-app├─ReplicaSet/civo-app-59488f449b└─ReplicaSet/civo-app-6b9557cdb8├─Pod/civo-app-6b9557cdb8-9kn9l True Current└─Pod/civo-app-6b9557cdb8-g79v7 True Current
The kubectl tree krew plugin showing the Deployment to ReplicaSet to Pod chain.
Getting a shell on a node
Sometimes the answer is not in a pod, it is on the node: a full disk, a kubelet problem, a sketchy iptables rule. There are two ways in, and which one is available depends on your provider.
Approach 1 (provider-agnostic): kubectl debug node
This works on any cluster where you have node-debug permissions, including managed clusters where you cannot SSH at all. It launches a throwaway pod in the node's host namespaces, with the node filesystem mounted at /host. Find the node name first with kubectl get nodes, then:
kubectl debug node/<node-name> -it --image=busybox
Once you are in the debug container, chroot into the host filesystem to get something close to a real node shell:
chroot /host
From there you can inspect the node almost as if you had SSHed in: check disk with df -h, look at processes (the container already shares the host PID, network, and IPC namespaces), and read kubelet logs. When you exit, the debug pod is cleaned up. Reaching the host this way returns exactly what you would expect:
node hostname: minikubekernel: 6.12.54-linuxkitoverlay 59G 23G 33G 42% /
kubectl debug node with chroot /host reading the node hostname, kernel, and disk.
Approach 2 (real SSH): only where the provider exposes node IPs
Here is the honest provider note. On Civo's managed Kubernetes service the worker nodes do not get a public IP and are not reachable by SSH; the cluster has a single public IP and the nodes never appear as SSH-able instances. So on a managed Civo cluster, use Approach 1.
Real SSH applies when you run k3s yourself on standalone Civo Compute instances (for example the k3sup pattern). In that case, each instance is a normal VM with a public IP, and if you attached your uploaded SSH key at creation time, you can connect directly:
# self-managed k3s on Civo Compute instances (NOT managed Civo Kubernetes)civo instance show -o custom -f public_ip <instance-name>ssh civo@<node-public-ip>
Rule of thumb: managed cluster, use kubectl debug node; your own k3s on Civo instances, plain SSH with your key works.
Summary
If you use Kubernetes at all, familiarity with the variety of things you can achieve with Kubectl is a must. Aside from the commands themselves, and the plugins like the ZSH one mentioned above, a set of very useful command-line tools exist to further enhance your cluster management and debugging experience. You can read about some of them in this post on cluster administration from the command line.

Technical Writer at Civo
Jubril Oyetunji is a DevOps engineer and technical writer with a strong focus on cloud-native technologies and open-source tools. His work centers on creating practical tutorials that help developers better understand platforms such as Kubernetes, NGINX, Rust, and Go.
As a contract technical writer, Jubril authored an extensive library of technical guides covering cloud-native infrastructure and modern development workflows. Many of his tutorials achieved strong search rankings, helping developers around the world learn and adopt emerging technologies.
Share this article
Further Reading
6 November 2023
Traffic management and deployment strategies with Istio
13 August 2025