Completed: Every container in the pod exited successfully
Completed means all containers finished with exit status 0 and will not be restarted. For a Job this is success. For a Deployment it usually means the workload was not meant to be long-running.
Applies to: All Kubernetes versions
What it means
A pod reaches the Succeeded phase — displayed as Completed by kubectl get pods — when all of its containers have terminated successfully and none will be restarted. This is the intended end state for Jobs and CronJobs. Seeing it under a Deployment means something different: a Deployment's pods have restartPolicy: Always, so a container that exits successfully is restarted, and the pod normally cycles into CrashLoopBackOff rather than settling at Completed. A genuinely completed pod under a long-running controller is therefore a signal that the workload type is wrong for what the container actually does.
Most common causes
- A Job or CronJob finished its work. This is normal and expected.
- A one-shot task deployed as a Deployment — the container does its work, exits 0, and is restarted forever.
- A container whose main process daemonises and exits, leaving nothing in the foreground. The runtime sees the parent exit and considers the container finished.
- A debugging or migration pod created manually that has done its job.
- A container whose entrypoint is a shell script that ends without exec-ing the real process.
How to diagnose it
- Check what owns the pod:
kubectl get pod POD -o jsonpath='{.metadata.ownerReferences[*].kind}'. A Job means this is normal; a ReplicaSet means it is not. - Confirm the exit code was actually 0:
kubectl describe pod PODunderLast State. - Read the logs to see whether the work was really done or the process merely returned early:
kubectl logs POD. - If a process daemonises, check the entrypoint — a foreground process is required for the container to stay alive.
How to fix it
- For genuine batch work, use a Job or CronJob. Set
ttlSecondsAfterFinishedso completed pods are cleaned up automatically instead of accumulating. - If a long-running process is daemonising, run it in the foreground. Most servers have a flag for this.
- If a shell entrypoint is exiting, use
execso the real process replaces the shell and becomes PID 1 — this also makes signal handling work correctly. - Nothing needs fixing for a completed Job. Its pods are retained on purpose so the logs remain readable.
Notes
Completed pods still hold their API objects and their logs on the node, so a CronJob running frequently with no history limits will accumulate them until they become their own problem. successfulJobsHistoryLimit and failedJobsHistoryLimit exist for this.
Related
- CrashLoopBackOff — Repeated restarts with increasing delay
- Exit code 0 — The process exited successfully
Sources
- Kubernetes documentation — Pod Lifecycle: pod phase
- Kubernetes documentation — Jobs
- Kubernetes documentation — CronJob