The kubectl commands you really need day to day fit on a handful of verbs: get and describe for looking, logs and exec for debugging, port-forward for testing, apply and delete for changing things. Once you have these seven down cold, along with switching context and namespace, you can handle most of your daily Kubernetes work with them. Everything else you look up.
What follows is sorted by situation rather than alphabetically, with real output and the pitfalls I have run into myself. At the end come the Kubernetes Dashboard and Lens, for the cases where a UI is faster than the terminal.
I work with kubectl daily, run Kubernetes clusters in production, and wrote the Kubernetes practical guide published by Rheinwerk Computing in 2024. You can find all my Kubernetes articles collected on the Kubernetes page.
How a kubectl command is structured
Every command follows the same pattern: kubectl <verb> <resource> <name> <options>. The verb says what you want to do (get, describe, delete), the resource says on what (pods, deployments, services), the name narrows it to one object, and the options control details like namespace or output format. Whatever you do not know by heart, kubectl tells you itself: kubectl --help lists all verbs, kubectl create --help shows examples, and kubectl create namespace --help tells you only a name is still missing. I still use this built-in help more than any search engine.
kubectl talks to your cluster's kube-apiserver. Which cluster that is and how kubectl authenticates there is stored in the kubeconfig at ~/.kube/config. With a local cluster from Minikube or kind, that file is already written for you; how such a cluster comes into being is covered in Local Kubernetes: a cluster on your machine. At a company you get it from the cluster admins. Treat it like a password: it holds the credentials for the cluster, often as an embedded certificate, and whoever has the file has the cluster.
Two small things save time every single day: an alias alias k=kubectl and the autocompletion kubectl ships for Bash, Zsh, Fish and PowerShell:
echo 'source <(kubectl completion zsh)' >> ~/.zshrc
After restarting your shell, the Tab key also completes your Pod names. With generated names like nginx-748c667d99-xtljp, that is the difference between typing and working.
Switching context and namespace safely
Most of the mistakes I have seen with kubectl were not wrong commands, they were correct commands run against the wrong cluster or namespace. That is why switching between them comes first.
A context in the kubeconfig ties together a cluster, a user, and a default namespace. kubectl config get-contexts shows all contexts and the active one, kubectl config use-context <name> switches. If you want to stop typing -n on every command, set the namespace permanently for the current context:
kubectl config set-context --current --namespace=my-k8s
Namespaces are separate areas in the cluster where names have to be unique and where you can assign permissions and quotas. A fresh cluster ships with four of them: default, kube-system, kube-public and kube-node-lease. I only use the default namespace for quick tests; every real application gets its own in my projects. With -A you see resources across all namespaces, useful when you are not sure where something is running.
For everyday work with several clusters I recommend kubectx and kubens: kubectx prod switches the cluster, kubens my-k8s the namespace, both with tab completion. And a habit from my own projects: type kubectl config current-context once before every delete or apply in production. It costs two seconds and has saved me more than once.
The most important kubectl commands in one table
This list covers most of my days. Anything not on it, I look up.
| Command |
What it does |
Typical options |
kubectl get pods |
Shows Pods in the current namespace with status and restarts |
-A, -o wide, -o yaml, -w |
kubectl describe pod <name> |
Details, state and events of an object |
works for any resource |
kubectl logs <pod> |
Prints a container's logs |
-f, --previous, -c, --since=10m |
kubectl exec -it <pod> -- sh |
Opens a shell or runs a command inside the container |
-c <container> |
kubectl port-forward pod/<pod> 8080:80 |
Tunnel from your machine to a Pod or Service |
svc/<name> instead of pod/<name> |
kubectl apply -f <file> |
Creates or updates from a manifest |
-f <folder>, --dry-run=server |
kubectl delete -f <file> |
Deletes what the manifest describes |
-l app=nginx, --force with caution |
kubectl create deployment nginx --image=nginx |
Creates an object quickly without a manifest |
--dry-run=client -o yaml as a template |
kubectl rollout status deployment/<name> |
Waits until a rollout is finished |
rollout undo, rollout history |
kubectl scale deployment/<name> --replicas=3 |
Changes the number of Pods |
|
kubectl get events --sort-by=.lastTimestamp |
History of what happened in the namespace |
-A |
kubectl api-resources |
Lists all resource types and their short names |
--namespaced=false |
kubectl explain pod.spec.containers |
Documentation for a field, right in the terminal |
|
kubectl config current-context |
Shows which cluster you are currently talking to |
use-context, get-contexts |
Looking and understanding: kubectl get and describe
kubectl get is the command you type most often. It shows you what exists in the cluster and what state it is in. For the system components of a Minikube cluster, it looks roughly like this:
kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-787d4945fb-qcsvv 1/1 Running 0 8d
etcd-minikube 1/1 Running 0 8d
kube-apiserver-minikube 1/1 Running 0 8d
kube-proxy-42gdl 1/1 Running 0 8d
kube-scheduler-minikube 1/1 Running 0 8d
storage-provisioner 1/1 Running 0 8d
The READY and RESTARTS columns matter most. 0/1 under READY means the container is not running or not ready; a rising number under RESTARTS means it is crashing repeatedly. -o wide adds the node and Pod IP, -o yaml gives you the complete object exactly as it sits in the cluster, and -w keeps the command open and streams changes live.
kubectl describe is your next step as soon as something is wrong. It summarizes an object's configuration, state, and above all its events. Whether an image could not be pulled, a node had no capacity, or a probe is failing shows up at the bottom of the output. What a Pod's fields mean is explained in Kubernetes Pod: what is a Pod?.
kubectl api-resources shows which resource types exist, including short forms (po, deploy, svc) and whether a resource is namespaced. A Pod is, a Persistent Volume or a Node is not, which is why you need -n for some and not for others.
kubectl logs: what the container has to say
Logs in Kubernetes are whatever the container writes to standard output. kubectl logs <pod> prints them, -f follows them live like tail -f. If more than one container runs in the Pod, pick one with -c <name>, or use --all-containers=true.
The option I show beginners most often is --previous. If a container has crashed, Kubernetes has likely already restarted it, and kubectl logs then only shows the logs of the new, still empty container. The reason for the crash is in the previous instance:
kubectl logs nginx-748c667d99-9448b --previous
With -l app=nginx you get the logs of every Pod with that label at once, handy for a Deployment with several replicas. --since=10m and --tail=100 limit the output. For searching across many Pods and longer time spans, kubectl is the wrong tool; that is what Loki and Grafana run for in my clusters. But for "why did this Pod just die", kubectl logs --previous is the first thing I type.
kubectl exec and port-forward: into the container and to the Pod
Sometimes reading is not enough and you need to get inside the container: check whether a config file arrived, whether DNS works, which environment variables are set. kubectl exec runs a command inside the container. Without a shell:
kubectl exec nginx-748c667d99-9448b -- ls /etc/nginx
With an interactive shell you leave again with exit:
kubectl exec -it nginx-748c667d99-9448b -- /bin/sh
The double dash separates the kubectl options from the command inside the container. Not every image ships a Bash; /bin/sh works almost everywhere. Slim images without any shell at all are the reason kubectl debug has existed for a while: the command attaches an extra container with tools to the running Pod without changing the image.
kubectl port-forward opens a tunnel from your machine to a Pod or Service, without needing an Ingress or a public address:
kubectl port-forward svc/nginx 8080:80
After that you reach the service in your browser at localhost:8080. I prefer forwarding to the Service rather than a single Pod, because the Service is still there after the Pod restarts. As long as the tunnel is open, the terminal is blocked. Ideal for development; in production I use it only for debugging, and only when the cluster's rules allow it.
Creating, changing, deleting: apply, create and delete
There are two ways to create resources. The imperative way tells Kubernetes what to do: kubectl create deployment nginx --image=nginx -n my-k8s creates a Deployment with an Nginx Pod, good for trying things out. The declarative way describes in a YAML file what the state should look like, and kubectl apply -f deployment.yaml makes the cluster match it: creating the object if it is missing, adjusting it if it already exists.
In my projects, practically everything runs through apply, since that keeps the manifests in Git with every change traceable. One trick connecting both worlds: kubectl create with --dry-run=client -o yaml writes you the manifest without creating anything:
kubectl create deployment nginx --image=nginx --dry-run=client -o yaml > deployment.yaml
kubectl replace replaces an object completely and needs the full manifest for that; I almost never need it, since apply serves the same purpose more gently. If you created an object with create and later change it with apply, kubectl warns about the missing last-applied-configuration. Harmless, but cleaner to use apply from the start.
kubectl delete deletes whatever you name: an object by name, everything from a file with -f, or several objects via a label with -l app=nginx. A Pod belonging to a Deployment comes right back after deletion, since the Deployment keeps the count steady. The --force option forces deletion of a Pod that no longer responds. I only use it when I know why the Pod is stuck, since it skips a clean shutdown.
Workflow: a Pod will not start
Here is how I approach a Pod that will not reach Running. First kubectl get pods for the status: Pending, ImagePullBackOff, CrashLoopBackOff or Error already tell part of the story. Then kubectl describe pod <name> and read the events at the bottom: for Pending, they usually say no node has enough resources; for ImagePullBackOff, that the image or registry credentials are wrong.
If the Pod is stuck on CrashLoopBackOff, the container does start, but it keeps crashing. Then kubectl logs <name> --previous is the most important command, because it holds the application's last error message. Only after that is kubectl exec worth running, to check inside the container whether the configuration and dependencies are there. The full troubleshooting flow for this case, including the role of probes and resource limits, is in CrashLoopBackOff: debugging Pods correctly.
Kubernetes Dashboard and Lens: when a UI helps
The Kubernetes Dashboard is the project's official web UI. It talks to the same API as kubectl and shows you Deployments, Pods and Services per namespace, plus logs, a shell into the container, and a YAML editor. With Minikube you start it with minikube dashboard; on other clusters it has to be installed, see the documentation for the Kubernetes Dashboard. It is good for a first overview, but a chore with several clusters, since each one needs its own web page and login.
Lens is a desktop application that reads the clusters from your kubeconfig and shows them all in one interface. What won me over: logs and a terminal open at the bottom while you navigate resources, port forwards running in the background, Prometheus metrics shown right in Lens when the cluster has them, and Helm charts installable straight from the UI.
Two honest caveats. Lens is a commercial product today, so check the licensing terms before using it at a company. The open alternative is Freelens, a fork of the unmaintained OpenLens, which came out of the open core of Lens Desktop. For the terminal, k9s is worth a look too, a fast interface right in the console.
|
kubectl |
Kubernetes Dashboard |
Lens |
| Runs where |
terminal |
browser, installed in the cluster |
desktop application |
| Multiple clusters |
via contexts |
one instance per cluster |
all from the kubeconfig |
| Logs and shell |
logs, exec |
yes, in the browser |
yes, in the window below |
| Port forward |
blocks the terminal |
no |
runs in the background |
| Scripting and automation |
yes |
no |
no |
| Cost |
free |
free |
depends on the license |
My own habit: kubectl for anything I want to repeat or script, and for all production work, where I want to type each command consciously. Lens or the Dashboard when I see an unfamiliar cluster for the first time. A UI shows the same data differently, it does not replace the commands.
Frequently asked questions
How do I switch the namespace permanently?
With kubectl config set-context --current --namespace=<name> you write the namespace into the active context of your kubeconfig, so it applies to every command without -n. The shorter way is kubens <name>, which does the same thing with tab completion across all existing namespaces.
How do I see the logs of a crashed container?
With kubectl logs <pod> --previous. After a crash, Kubernetes restarts the container, and without the option you only see the logs of the new instance. --previous shows the output of the last terminated instance, which holds the actual error message.
What is the difference between kubectl apply and create?
create creates an object and fails if it already exists. apply creates or updates depending on what is already in the cluster and remembers the last applied configuration. For manifests living in Git and changing over time, apply is right; create suits quick experiments.
How do I get into a running container?
With kubectl exec -it <pod> -- /bin/sh you open a shell inside the container, picking one with -c <name> if the Pod has several. If the image has no shell at all, kubectl debug helps, attaching an extra container with tools to the Pod.
How do I delete a Pod stuck in Terminating?
First check with kubectl describe pod <name> why it is stuck, often a finalizer or a volume. Once the cause is clear, kubectl delete pod <name> --force --grace-period=0 forces the deletion, skipping a clean shutdown, so only do it once you know what the Pod can no longer finish.
Where to go next
If you have not tried these commands on your own cluster yet, that is the natural next step: a local cluster is set up in a few minutes, and the instructions are in Local Kubernetes: a cluster on your machine. Create a namespace there, roll out an Nginx with kubectl create deployment, and work your way once through every layer with get, describe, logs, exec and port-forward. What you learn about a Pod's structure along the way is covered in Kubernetes Pod: what is a Pod?.
The project maintains the complete reference of every verb and option in the kubectl documentation on kubernetes.io. The table above is enough for daily work; you will find the rest when you need it.
In full detail, from the installation for each operating system through building the kubeconfig to a tour of Dashboard and Lens, this is covered in chapter 2 of my book "Kubernetes Practical Guide" (Rheinwerk Computing). You can find all the information about the book on my Kubernetes page.