2024-12-21
8 min read

Why Your Docker Container Exits Immediately

Why Your Docker Container Exits Immediately

TLDR: Docker containers exit immediately when their main process (defined by CMD or ENTRYPOINT) completes or fails. Common causes include running background services without keeping them in the foreground, syntax errors in commands, missing files, or using shell scripts that exit too quickly. Use docker logs to see why it exited and run processes in foreground mode or use tools like tail -f to keep the container alive.

When you run a Docker container and it immediately exits, you see something like this:

docker run myapp
# Returns to prompt immediately

docker ps
# CONTAINER ID   IMAGE   COMMAND   CREATED   STATUS   PORTS   NAMES
# (empty - no running containers)

docker ps -a
# Shows container with status "Exited (0) 2 seconds ago"

The container started but stopped right away. Here's why this happens and how to fix it.

How Docker Containers Work

A Docker container runs as long as its main process is running. When that process exits, the container stops:

Container lifecycle:
1. docker run myapp
2. Container starts
3. Runs CMD or ENTRYPOINT command
4. Command completes/exits
5. Container stops automatically

If CMD exits in 1 second → Container runs for 1 second
If CMD runs forever → Container runs forever

Unlike virtual machines that boot an OS and keep running, containers are designed to run a single application process. When that process ends, there's nothing to keep the container alive.

Common Causes and Fixes

Running Background Services

The most common mistake is starting a service in background mode:

# Wrong - starts nginx in background and exits
FROM nginx:alpine
CMD ["nginx"]

When you run this, nginx starts but daemonizes (runs in background), so the main process exits immediately:

docker run nginx-app
# Container exits immediately because nginx daemonized

Fix it by running the service in foreground mode:

# Correct - nginx stays in foreground
FROM nginx:alpine
CMD ["nginx", "-g", "daemon off;"]

The -g "daemon off;" flag tells nginx not to daemonize. Now the nginx process stays attached to the container:

docker run -d nginx-app
# Container keeps running because nginx is in foreground

Other services have similar flags:

# Apache in foreground
CMD ["apache2-foreground"]

# Redis in foreground (default behavior)
CMD ["redis-server"]

# PostgreSQL
CMD ["postgres"]

# Node.js app (naturally runs in foreground)
CMD ["node", "server.js"]

Using Shell Scripts That Exit

If your CMD runs a shell script that completes quickly:

FROM ubuntu:22.04
COPY setup.sh /setup.sh
RUN chmod +x /setup.sh
CMD ["/setup.sh"]

If setup.sh looks like this:

#!/bin/bash
echo "Setting up..."
export MY_VAR=value
echo "Setup complete"
# Script exits here

The container exits as soon as the script finishes. To keep it running, add a long-running process:

#!/bin/bash
echo "Setting up..."
export MY_VAR=value
echo "Setup complete"

# Keep container alive by running your application
exec /usr/bin/myapp

The exec command replaces the shell process with your application, making it PID 1 in the container.

CMD vs ENTRYPOINT Confusion

Understanding the difference prevents common mistakes:

# CMD can be overridden completely
FROM ubuntu:22.04
CMD ["echo", "Hello"]
# Uses CMD
docker run myapp
# Output: Hello

# CMD is replaced by your command
docker run myapp ls /
# Runs 'ls /' instead of 'echo Hello'
# Container exits after ls completes

With ENTRYPOINT:

# ENTRYPOINT is not replaced, CMD becomes arguments
FROM ubuntu:22.04
ENTRYPOINT ["echo"]
CMD ["Hello"]
# Runs: echo Hello
docker run myapp
# Output: Hello

# Runs: echo Goodbye (CMD is replaced, ENTRYPOINT is not)
docker run myapp Goodbye
# Output: Goodbye

For applications that should always run, use ENTRYPOINT:

# App always runs, arguments can be passed
FROM node:18
WORKDIR /app
COPY . .
ENTRYPOINT ["node", "server.js"]
CMD ["--port", "3000"]

No Command Specified

If you don't specify CMD or ENTRYPOINT and the base image doesn't have one:

FROM ubuntu:22.04
# No CMD or ENTRYPOINT
docker run myapp
# Container starts with default CMD from ubuntu image (usually /bin/bash)
# But there's no terminal attached, so bash exits immediately

Fix by adding a command:

FROM ubuntu:22.04
CMD ["tail", "-f", "/dev/null"]
# Keeps container running by tailing nothing (useful for debugging)

Or run interactively:

# -i = interactive, -t = allocate pseudo-TTY
docker run -it myapp /bin/bash
# Now you have a shell and container stays running

Application Errors

If your application crashes on startup, the container exits with a non-zero exit code:

FROM python:3.11
WORKDIR /app
COPY app.py .
CMD ["python", "app.py"]

If app.py has an error:

import missing_module  # This will fail
print("Hello")
docker run myapp
# Container exits immediately

docker ps -a
# STATUS: Exited (1) 2 seconds ago
# Exit code 1 indicates an error

# Check what went wrong
docker logs <container_id>
# ModuleNotFoundError: No module named 'missing_module'

Always check logs when a container exits unexpectedly:

# Get the container ID
docker ps -a | head -2

# View logs
docker logs <container_id>

# Or use container name
docker logs myapp

Missing Files or Permissions

If your CMD references a file that doesn't exist:

FROM ubuntu:22.04
CMD ["/app/start.sh"]

But you never copied start.sh:

docker run myapp
# Exits immediately

docker logs <container_id>
# /bin/sh: 1: /app/start.sh: not found

Or the file exists but isn't executable:

FROM ubuntu:22.04
COPY start.sh /app/start.sh
# Forgot to make it executable
CMD ["/app/start.sh"]

Fix by setting executable permissions:

FROM ubuntu:22.04
COPY start.sh /app/start.sh
RUN chmod +x /app/start.sh
CMD ["/app/start.sh"]

Debugging Strategies

Check Container Logs

# View logs of exited container
docker logs <container_id>

# Follow logs in real-time (for running container)
docker logs -f <container_id>

# Show last 50 lines
docker logs --tail 50 <container_id>

# Show timestamps
docker logs -t <container_id>

Check Exit Code

The exit code tells you what happened:

docker ps -a
# STATUS column shows exit code

# Exit code meanings:
# 0   = Success (normal exit)
# 1   = Application error
# 125 = Docker daemon error
# 126 = Command cannot be executed
# 127 = Command not found
# 137 = Killed (SIGKILL)
# 143 = Terminated (SIGTERM)

Inspect Container Configuration

# See the full container configuration
docker inspect <container_id>

# See just the command that was run
docker inspect --format='{{.Config.Cmd}}' <container_id>

# See the entrypoint
docker inspect --format='{{.Config.Entrypoint}}' <container_id>

Override CMD to Debug

Run the container with a shell to investigate:

# Override CMD and get a shell
docker run -it myapp /bin/bash

# Now you can test commands manually
ls /app
cat /app/start.sh
/app/start.sh
# See what fails

Keep Container Running for Debugging

Temporarily modify the Dockerfile:

FROM ubuntu:22.04
COPY app.sh /app.sh

# Comment out the real CMD
# CMD ["/app.sh"]

# Use this while debugging
CMD ["tail", "-f", "/dev/null"]

Build and run:

docker build -t myapp-debug .
docker run -d --name debug myapp-debug

# Execute commands inside the running container
docker exec -it debug /bin/bash
# Now debug interactively

Real-World Examples

Web Server That Exits

# Problem: Apache exits immediately
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y apache2
CMD ["apache2"]

Fix:

FROM ubuntu:22.04
RUN apt-get update && apt-get install -y apache2
# Use the foreground script
CMD ["apachectl", "-D", "FOREGROUND"]

Python Application That Exits

# Problem: Python script runs and exits
FROM python:3.11
COPY script.py .
CMD ["python", "script.py"]

If script.py is:

print("Starting...")
# Does some work
print("Done")
# Script exits

Fix by making it a long-running server:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    return "Hello World!"

if __name__ == '__main__':
    # This keeps running
    app.run(host='0.0.0.0', port=5000)

Database Container

# MySQL container - correct approach
FROM mysql:8.0
ENV MYSQL_ROOT_PASSWORD=mypassword
# No CMD needed - base image has: CMD ["mysqld"]

The MySQL base image already has the correct CMD that keeps mysqld running in the foreground.

If you must run multiple services, use a process manager:

FROM ubuntu:22.04
RUN apt-get update && apt-get install -y supervisor nginx redis-server

COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# supervisord runs in foreground and manages other services
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

supervisord.conf:

[supervisord]
nodaemon=true

[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true

[program:redis]
command=/usr/bin/redis-server
autostart=true
autorestart=true

However, it's better practice to use separate containers for each service and link them with Docker Compose.

Quick Fixes Reference

# Container exits immediately after start
→ Check logs: docker logs <container>
→ Check exit code: docker ps -a
→ Override CMD: docker run -it myapp /bin/bash

# Service starts but container exits
→ Run service in foreground mode
→ Check service has "-g daemon off" or equivalent

# Script runs and exits
→ Add 'exec' before final command
→ Add a long-running process at the end

# Command not found error
→ Verify file exists in container
→ Check file has execute permissions
→ Use absolute path

# To keep container alive for debugging
→ Add: CMD ["tail", "-f", "/dev/null"]
→ Or: CMD ["sleep", "infinity"]

The key to preventing containers from exiting immediately is making sure the main process stays in the foreground and doesn't complete. Check logs first, understand what command is running, and adjust it to remain active for the container's intended lifetime.

Published: 2024-12-21|Last updated: 2024-12-21T10:00:00Z

Found an issue?