Kubernetes Exit Code 0
Causes and Fixes
Exit code 0 means the container's main process terminated successfully. While this is not an error, it can cause unexpected behavior when a Deployment or ReplicaSet pod exits with code 0 and the restartPolicy is set to Always, resulting in a CrashLoopBackOff as Kubernetes continuously restarts a container that keeps 'completing' successfully.
Symptoms
- Pod shows CrashLoopBackOff but logs show no errors
- Container exit code is 0 in kubectl describe pod output
- Container runs a task and exits cleanly, then gets restarted
- Restart count increases even though the application did not crash
- Pod events show 'Back-off restarting failed container' despite clean exit
Common Causes
Step-by-Step Troubleshooting
1. Confirm the Exit Code
kubectl describe pod <pod-name>
Look for:
Last State: Terminated
Reason: Completed
Exit Code: 0
The Completed reason with exit code 0 means the process finished successfully.
2. Check the Container Logs
kubectl logs <pod-name> --previous
The logs should show no errors — the application simply completed its work and exited. This confirms the issue is about the workload type, not a bug.
3. Determine the Workload Type
Is this supposed to be a long-running service or a one-shot task?
Long-running service (web server, API, worker):
- Should run indefinitely as a Deployment or StatefulSet
- Must have a foreground process that stays alive
One-shot task (migration, data import, cleanup):
- Should run once and exit as a Job
- Exit code 0 means success
4. Fix: Convert to a Job (For One-Shot Tasks)
If the workload is meant to run once and exit:
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration
spec:
backoffLimit: 3
activeDeadlineSeconds: 600
ttlSecondsAfterFinished: 3600
template:
spec:
restartPolicy: OnFailure
containers:
- name: migrate
image: myapp:v1
command: ["./migrate.sh"]
# Delete the Deployment
kubectl delete deployment <deploy-name>
# Create the Job
kubectl apply -f job.yaml
# Monitor the Job
kubectl get jobs -w
5. Fix: Keep the Process in the Foreground (For Services)
If the workload should be a long-running service, ensure the main process runs in the foreground.
Problem: Background process
# BAD: Application starts in background, script exits
CMD ["sh", "-c", "myapp &"]
Fix: Foreground process
# GOOD: Application runs in foreground as PID 1
CMD ["myapp"]
Problem: Wrapper script without exec
#!/bin/sh
# BAD: Shell starts app as child process, then shell exits
myapp --config /etc/app/config.yaml
#!/bin/sh
# GOOD: exec replaces the shell with the app process
exec myapp --config /etc/app/config.yaml
6. Fix: Shell Script Entrypoint
A common pattern is a shell script that sets up environment and then runs the app. Always use exec for the final command.
#!/bin/sh
set -e
# Setup tasks
echo "Configuring application..."
envsubst < /etc/app/template.yaml > /etc/app/config.yaml
# Start the application (MUST use exec)
exec java -jar /app/application.jar
Without exec, the shell is PID 1 and the Java process is a child. When the shell finishes executing (after the java command is backgrounded or after the script logic is complete), the container exits.
7. Debug the Container Locally
Test the container to see if it stays running.
# Run the container and check if it exits
docker run --rm <image>
# Check the exit code
echo $?
# If it exits immediately, check what the entrypoint does
docker run --rm --entrypoint sh <image> -c "cat /entrypoint.sh"
8. Check for Missing Foreground Flags
Some applications need specific flags to run in the foreground:
# nginx needs daemon off
command: ["nginx", "-g", "daemon off;"]
# Apache httpd needs -D FOREGROUND
command: ["httpd", "-D", "FOREGROUND"]
# Redis uses foreground by default but check config
command: ["redis-server", "--daemonize", "no"]
9. Verify the Fix
# Watch the pod
kubectl get pods -w
# Verify it stays running
kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[0].restartCount}'
# Should be 0 or not increasing
# Check the container state
kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[0].state}'
# Should show: {"running":{"startedAt":"..."}}
The pod should reach Running state and stay there without restarting.
How to Explain This in an Interview
I would explain that exit code 0 means success from the process's perspective, but Kubernetes treats any exit as a reason to restart when the restart policy is Always (the default for Deployments). The key insight is matching the workload type to the correct Kubernetes resource: use Deployments for long-running services, Jobs for one-shot tasks, and CronJobs for scheduled tasks. I would also explain the PID 1 requirement — the container's main process must run in the foreground as PID 1 to keep the container alive.
Prevention
- Use Jobs for one-shot tasks and CronJobs for scheduled tasks
- Ensure the container entrypoint runs a long-lived foreground process
- Use exec in wrapper scripts to replace the shell with the application process
- Test containers locally with docker run to verify they stay running
- Set restartPolicy appropriately for the workload type