Learn the kubectl commands you will run daily: get, apply, describe, delete, selectors, output formats, namespaces and tips to work faster with kubeconfig.
kubectl is a REST client. Every command turns into an HTTP request to the API server, authenticated with credentials from your kubeconfig.
Internalising that explains a lot. Every kubectl verb maps to an HTTP method, anything you can do in a manifest you can do through the API, and permission errors are RBAC decisions, not client bugs.
Request flow: kubectl → running pod
Validates & persists to etcd
Pulls image & starts container
✓ Desired state achieved
Validates & persists to etcd
Pulls image & starts container
✓ Desired state achieved
~/.kube/config holds clusters, users and contexts. A context pairs a cluster with credentials and a default namespace.
Check your context before running anything destructive. Running a command against the wrong cluster is the most common serious mistake people make with Kubernetes, and nothing in the output warns you. Tools like kubectx, or a shell prompt showing the current context, are worth the five minutes to set up.
kubectl config current-context # which cluster am I pointed at?
kubectl config get-contexts # all of them
kubectl config use-context prod # switchTwo ways to work, and the distinction matters more than it first appears.
Imperative: tell the cluster to do something now:
Fast, good for learning and debugging, and untracked. Nothing records why the cluster is in this state, and nobody can reproduce it.
Declarative: write the desired state in a file, apply it:
The file is reviewable, diffable, version-controlled, and reproducible. This is how anything real should be managed.
The productive middle path is to generate manifests imperatively and then keep them:
That gives you a valid skeleton in seconds without memorising the schema. Edit it, commit it, apply it.
kubectl create deployment web --image=nginx:1.27
kubectl scale deployment web --replicas=3
kubectl expose deployment web --port=80apply performs a three-way merge between the last applied configuration, your file, and the live object. That is why it can update one field without discarding changes made by controllers, and why mixing apply with kubectl edit produces confusing results.
Preview before applying to anything that matters:
--dry-run=server validates against the real API server including admission controllers, so it catches far more than client-side validation.
kubectl diff -f deployment.yaml
kubectl apply -f deployment.yaml --dry-run=serverkubectl describe is the single highest-value command. The Events section at the bottom usually states the problem in plain language. Failed scheduling, image pull errors, probe failures, volume mount problems.
# What is here?
kubectl get pods
kubectl get pods -o wide # + IPs and nodes
kubectl get pods -A # every namespace
kubectl get pods -w # watch changes live
kubectl get all # common resource types in one view
# What is wrong with it?
kubectl describe pod <name> # spec, status and events. Start here
kubectl logs <name>
kubectl logs <name> --previous # the run that crashed
kubectl logs <name> -f # follow
kubectl logs -l app=web --tail=50 # across all matching Pods
# Get inside
kubectl exec -it <name> -- sh
kubectl port-forward pod/<name> 8080:80
# Change things
kubectl apply -f manifest.yaml
kubectl scale deployment/web --replicas=5
kubectl rollout restart deployment/web
kubectl delete -f manifest.yamlLabel selectors are how everything in Kubernetes finds everything else, so they are worth being fluent in. Most commands accept -l, including logs and delete.
kubectl get pods -l app=web # by label
kubectl get pods -l 'env in (staging,prod)' # set-based
kubectl get pods --field-selector status.phase=Running
kubectl get pods --sort-by=.metadata.creationTimestamp
kubectl get pods --show-labelsTwo genuinely useful one-liners:
kubectl get pod web-abc -o yaml # the full object as stored
kubectl get pod web-abc -o json
# Extract one value
kubectl get pod web-abc -o jsonpath='{.status.podIP}'
# Build a custom table
kubectl get pods -o custom-columns=\
NAME:.metadata.name,\
STATUS:.status.phase,\
NODE:.spec.nodeName,\
RESTARTS:.status.containerStatuses[0].restartCountForgetting the namespace is the most common reason kubectl get pods returns "No resources found" when you know something is running.
kubectl get pods -n kube-system
kubectl get pods -A
# Stop typing -n on every command
kubectl config set-context --current --namespace=paymentskubectl explain documents any field from the live API server, which beats searching for documentation that may describe a different version than you are running.
# Shell completion. Do this once, it pays for itself immediately
source <(kubectl completion bash) # or zsh
echo 'source <(kubectl completion bash)' >> ~/.bashrc
# The near-universal alias
alias k=kubectl
complete -o default -F __start_kubectl kYou run kubectl create -f deployment.yaml, edit the replica count in the file, and run the same command again. What happens?
kubectl get pods returns "No resources found in default namespace" but a colleague confirms the application is running. What is the most likely explanation?
Inspect cluster resources with kubectl get and describe. Use -o wide, jsonpath and custom-columns, read events, and know when to choose get over describe.
Learn kubectl apply vs create vs replace, preview changes with kubectl diff, use server-side apply, delete resources safely and fix objects stuck deleting.
Debug pods with kubectl logs (including --previous), kubectl exec, port-forward and kubectl cp. Handle distroless images with kubectl debug.