Docker Container Exiting Immediately? Here is How to Keep It Running

beginner🐳 Docker2026-04-03| Docker Engine (All Versions), Docker Compose, Linux, macOS, WSL2

Error Message

Container shuts down instantly (e.g., `docker ps -a` shows 'Exited (0)' or 'Exited (1)')
#docker#devops#troubleshooting#sysadmin

The 'Now You See It, Now You Don't' Container

You hit docker run, the terminal returns a container ID, and everything seems fine. But a second later, your service is unreachable. You run docker ps and see an empty list. Finally, docker ps -a reveals the truth: your container died almost the moment it was born.

# What you see in the terminal
CONTAINER ID   IMAGE         COMMAND                  STATUS                     NAMES
7a1b2c3d4e5f   my-app:latest "docker-entrypoint.s…"   Exited (0) 5 seconds ago   my-app-container

Docker containers stay alive only as long as their primary process (PID 1) is running. If that process finishes its task or hits a snag and crashes, the container stops. It is not a bug; it is how Docker is designed to work. Here is how to diagnose and fix the most common causes.

Step 1: Get the Story from the Logs

Don't start changing code blindly. Even if a container is in the 'Exited' state, Docker usually preserves the STDOUT and STDERR streams. Your first move should be checking the last 20 to 50 lines of output.

docker logs --tail 50 <container_id_or_name>

Look for specific clues like stack traces, "Permission denied," or missing configuration files. If the logs are completely empty and you see Exited (0), the container likely performed a quick task (like ls or a short script) and exited because it had nothing left to do.

Step 2: Keep Interactive Containers Alive

Base images like ubuntu, alpine, or debian are built to provide a shell. If you run them without an interactive terminal, the shell sees no input and exits immediately to save resources.

The Quick Fix for One-Off Runs

Use the -it flags. This tells Docker to keep the standard input open and allocate a pseudo-TTY:

docker run -it ubuntu /bin/bash

The Docker Compose Solution

If you are using Compose to build a development environment, add these two lines to your service definition. This mimics the -it behavior and prevents the container from closing the session.

services:
  app:
    image: ubuntu
    tty: true
    stdin_open: true

The "Infinite Loop" Trick

Sometimes you need a container to stay up in the background just so you can exec into it later. In these cases, override the default command with a process that never ends:

services:
  app:
    image: alpine
    command: tail -f /dev/null

Step 3: Troubleshooting Exit Code 1 (App Crashes)

An Exited (1) status means the application tried to run but encountered a fatal error. In roughly 90% of cases, the culprit is one of three things:

  • Missing Environment Variables: Your app expects a DATABASE_URL but it wasn't defined in your environment block.
  • Volume Permissions: You mounted a local folder to /var/log/app, but the container user doesn't have permission to write to your host machine.
  • Incorrect WORKDIR: The CMD is looking for index.js in the root, but your WORKDIR is set to /usr/src/app.

Pro Tip: Always verify your paths. If you use an entrypoint script, ensure it has the correct execution permissions before building the image:

# Inside your Dockerfile
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

Step 4: The Windows-to-Linux Line Ending Trap

If you develop on Windows and run containers on Linux, your scripts might be saved with CRLF line endings. Linux doesn't recognize these. This often results in a confusing "file not found" error, even when the file is clearly visible.

You can fix this by forcing your IDE to use LF endings or by adding dos2unix to your build process:

# A robust way to handle line endings in a Dockerfile
RUN apt-get update && apt-get install -y dos2unix
RUN dos2unix /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

Step 5: Use the JSON 'Exec' Form

How you write your CMD matters. Avoid the shell form (CMD node index.js) because it wraps your command in /bin/sh -c. This prevents your app from receiving Unix signals like SIGTERM.

# Preferred: Exec form
CMD ["node", "index.js"]

The Exec form ensures your application is PID 1. This allows for graceful shutdowns and prevents "zombie" processes from hanging around after an error.

How to Verify the Fix

After applying your changes, don't just assume it works because the command didn't error out. Check the uptime after 30 seconds. Use docker ps to ensure the status shows Up 30 seconds (or longer). If you have a health check defined, verify it by running docker inspect --format='{{json .State.Health.Status}}' <container_id>. A status of "healthy" is the ultimate confirmation that your process is actually doing its job.

Related Error Notes