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

1
One-shot task in a Deployment
A script or migration task that completes and exits is deployed as a Deployment (restartPolicy: Always) instead of a Job (restartPolicy: OnFailure or Never).
2
Application exits after initialization
The main process finishes its work and exits without staying resident. Long-running services must have a foreground process.
3
Entrypoint runs in background
The container's entrypoint script starts the application in the background and then the script exits. The main process must run in the foreground.
4
Shell script without exec
A wrapper script starts the application without using exec, so the shell process (PID 1) exits after launching the child process.
5
Incorrect command override
The pod spec overrides the command with something that exits immediately (e.g., echo, env, or a missing command that defaults to a no-op).

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

Related Errors