All of these produce the same CrashLoopBackOff status. The exit code and the events tell them apart.
Application error on startup
Usually exit code 1
The process started and decided it could not continue: a missing environment variable, an unreachable database, a config file that does not parse. This is the most common cause by a wide margin, and the reason is printed in the logs of the instance that died.
What to do: Read the previous logs. The answer is almost always in the first ten lines.
kubectl logs <pod> --previous --tail=50
OOMKilled
Exit code 137 with reason OOMKilled
The container exceeded its memory limit during startup. A JVM is the classic case: older JVMs read the host's total memory rather than the cgroup limit and size a heap far larger than the container is permitted, so the process is killed before it finishes booting.
What to do: Raise limits.memory, or set the heap explicitly against the limit. Note that a memory-backed emptyDir, including /dev/shm, counts towards the limit.
kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'
Exit code 0
It succeeded, and that is the problem
A Deployment's pod template must use restartPolicy: Always, so a container that completes successfully is started again immediately. Do that a few times and the pod reports CrashLoopBackOff without anything ever having failed. The usual cause is a process that daemonises itself, or a workload that is genuinely a batch job.
What to do: If it is meant to finish, make it a Job. If it is meant to stay up, stop the process forking into the background.
Liveness probe failing
The container is killed, not crashing
The application is fine and the liveness probe says otherwise, so the kubelet restarts it, repeatedly. The tell is in the events rather than the logs: the logs look like a clean startup followed by a clean shutdown, over and over. A liveness probe with too short an initialDelaySeconds on a slow-starting application produces exactly this, and it can never recover on its own.
What to do: Add a startupProbe so liveness does not begin until the application is up. Increase initialDelaySeconds only as a stopgap: it delays every later check too.
kubectl describe pod <pod> | grep -i "liveness\|unhealthy"
Missing ConfigMap or Secret
Often CreateContainerConfigError, not CrashLoopBackOff
A referenced ConfigMap or Secret does not exist, or the key inside it does not. If it is mounted or referenced with configMapKeyRef the pod never starts and the status is CreateContainerConfigError. With envFrom and a key that is not a valid shell identifier, the key is skipped silently and the application starts without it, which then fails as an ordinary application error.
What to do: Check the events, then check the referenced object exists in the same namespace.
kubectl describe pod <pod> | sed -n '/Events:/,$p'
Read-only root filesystem
The container dies on its first temp file
readOnlyRootFilesystem: true without a writable /tmp kills most images the moment they try to write anything, which for many runtimes is during startup. The error is a permission denied on a path nobody thought about.
What to do: Mount an emptyDir at /tmp, and at any other path the process writes to.
Wrong command or entrypoint
Exit code 127 or 126
The command does not exist in the image, or cannot be executed. 127 is not found, 126 is found but not runnable. A shell script with Windows line endings produces 127 and is invisible in every diff, because the interpreter line reads as /bin/sh followed by a carriage return.
What to do: Check the binary exists and the script has Unix line endings. Also remember that in Kubernetes, command replaces the image's ENTRYPOINT and args replaces its CMD.