Common Gotchas
Kubernetes fails in recognizable patterns with recognizable names. Learn the five below and
kubectl get pods output stops being cryptic — each status points at a specific
cause and a specific next command.
💡 The Bottom Line
The debugging path is always the same: kubectl get pods for the status,
kubectl describe pod for the Events, kubectl logs (with
--previous for crashed attempts) for the app's own words. The status tells you
which of the three will hold the answer.
1. CrashLoopBackOff
The container starts, crashes, gets restarted, crashes again — and Kubernetes backs off,
waiting longer between attempts. The problem is inside your app or its config:
bad env var, unreachable database, missing file, immediate exception on boot.
kubectl logs <pod> --previous shows the dying attempt's output — the answer
is almost always right there.
2. ImagePullBackOff / ErrImagePull
The node can't download your image. Three causes cover nearly every case: a typo in the
image name or tag, the tag doesn't exist in the registry (did CI actually push it?), or the
registry is private and the pod lacks an imagePullSecrets credential.
kubectl describe pod's Events section quotes the exact registry error.
3. Pending forever
The pod is accepted but no node can host it. describe tells you why the
scheduler is stuck — most commonly "Insufficient cpu/memory" (the pod's
requests ask for more than any node has free), or a volume/affinity constraint
that can't be satisfied. Locally: your minikube VM is just too small.
4. No resource limits — the quiet one
Pods without resources.requests and limits run fine — until one
leaks memory and starves everything else on the node, or the scheduler packs a node past its
real capacity because nothing declared what it needed. Every production pod template should
declare both:
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 256Mi
A pod exceeding its memory limit is killed with status OOMKilled — worth recognizing on sight: it means "raise the limit or fix the leak," not "Kubernetes is flaky."
5. Service selector mismatch
From Lesson 4, but it earns its spot in the gotcha list: the Service exists, DNS resolves,
and every request times out — because the selector matches zero pods.
kubectl get endpoints <service>: empty means label mismatch. One typo,
total outage, no error message anywhere.
🙋 There Are No Dumb Questions
Q: My pod is Running with 0 restarts, but the app still doesn't work. Now what?
A: "Running" only means the process is alive. Work the chain: kubectl logs for
app errors, kubectl exec -it <pod> -- sh to poke around inside (can it
reach its database?), kubectl get endpoints to check the Service wiring,
kubectl port-forward to test the pod directly and bypass the Service. Each step
isolates one layer — pod, dependencies, Service — until the broken one is obvious.
⚠️ Watch Out
Resist the reflex to fix things by deleting pods until the error disappears. A deleted
pod's replacement often hits the same problem — you've destroyed the evidence and gained
nothing. describe and logs --previous exist precisely so you can
diagnose before anything is deleted. Delete-as-debugging is the Kubernetes
equivalent of turning it off and on again — sometimes it works, but you learn nothing.
🔧 Try It Yourself
Cause two of the gotchas on purpose, so you recognize them relaxed instead of at 3 a.m.:
kubectl run broken1 --image=nginx:no-such-tag
kubectl run broken2 --image=busybox -- sh -c "exit 1"
kubectl get pods -w
Watch broken1 reach ImagePullBackOff and broken2
reach CrashLoopBackOff, then read kubectl describe pod broken1 and
kubectl logs broken2 --previous. Clean up with
kubectl delete pod broken1 broken2.
✅ Quick Check
A pod shows CrashLoopBackOff. Where does the problem almost certainly live?
That's the track. The recap gathers the whole model — desired state, pods, Deployments, Services, config — into one page, plus where to go next.