TechEarl

Docker Container Exits Immediately — Diagnosing the No-Foreground-Process Problem

Your container starts, then exits with code 0 (or 1, or 137) before you can do anything. The root cause is almost always one of three things: no foreground process, a crash, or OOM. Here's how to tell which.

Ishan KarunaratneIshan Karunaratne⏱️ 8 min readUpdated
Share thisCopied

You run docker run -d --name app my-image, the container appears in docker ps -a as "Exited," and nothing else happened. This is one of the most common "what just happened?" moments in Docker. The cause is almost always one of three things: the container has no foreground process, the main process crashed, or it got OOM-killed. Each leaves a different signature.

First step: read the logs and the exit code

bash
docker logs CONTAINER_NAME
docker inspect --format '{{.State.ExitCode}}' CONTAINER_NAME
docker inspect --format '{{.State.OOMKilled}}' CONTAINER_NAME

The exit code is the smoking gun:

Exit codeWhat it means
0The process completed normally — it ran, did its thing, and exited. Usually means the container had no foreground service.
1125Application error. The process started but errored out. docker logs shows the message.
126Command found but not executable. Usually a missing executable bit on a script.
127Command not found. Often a typo or a missing binary on alpine.
137SIGKILL (128 + 9). On --memory limits, this is OOMKilled. docker inspect's OOMKilled field confirms.
143SIGTERM (128 + 15). From docker stop or similar. Usually expected.

Case 1: exit code 0 — no foreground process

The most common cause. The container's main process ran, did nothing useful, exited cleanly. Three flavors:

Flavor A: you ran a non-interactive command.

bash
docker run -d ubuntu:24.04
# Exits immediately

ubuntu:24.04's default CMD is bash. Without -it, bash has no terminal, no input, no work to do — it exits. Solution: -it for interactive shells, or pass a long-running command (docker run -d ubuntu:24.04 sleep 3600 for a placeholder).

Flavor B: the image expects a command and you didn't give one.

Some images ship with a CMD ["bash"] or similar that only makes sense interactively. Without arguments and -it, they exit.

Flavor C: a server that doesn't actually start as a server.

You wrote CMD ["./my-server"] in your Dockerfile and ./my-server exits because it can't find its config, or the port it wanted is taken, or it's actually a CLI tool you meant to use differently.

docker logs is what tells you which:

bash
docker logs CONTAINER_NAME

If the logs are empty and exit code is 0, you have flavor A or B. If the logs show errors, you have flavor C.

Case 2: exit code 1-125 — application crash

bash
docker logs CONTAINER_NAME

The error message is in the logs. Common ones:

  • Error: ENOENT: no such file or directory — your CMD references a path that doesn't exist in the image. Check that the build COPY put the file where the CMD expects.
  • MODULE_NOT_FOUND (Node) — node_modules is missing or your bind mount overlaid it with empty / wrong-arch content.
  • Database connection refused — service is starting before the database is ready. Add a healthcheck with depends_on: condition: service_healthy. See Restart Policies and Health Checks.
  • bind: address already in use — port conflict. See Port is Already Allocated.
  • PHP fatal errors, Python tracebacks — code bug. Same as it would be outside Docker.

If the logs are empty and the exit code is non-zero, the process crashed before producing output. Try running with -it and the entrypoint overridden:

bash
docker run --rm -it --entrypoint sh my-image
# Then inside the container, manually run the command:
# ./my-server
# See what happens.

Case 3: exit code 137 — OOMKilled

bash
docker inspect --format '{{.State.OOMKilled}}' CONTAINER_NAME

If that returns true, the Linux OOM-killer killed your process because the container exceeded its memory limit. Either:

  • The container has a --memory limit and the process exceeded it. Raise the limit, or fix the memory leak.
  • The host ran out of memory and the kernel killed Docker containers preferentially.

docker stats shows live memory usage:

bash
docker stats CONTAINER_NAME

For exited containers, docker inspect shows the memory limit that was set:

bash
docker inspect --format '{{.HostConfig.Memory}}' CONTAINER_NAME

Common in Compose stacks where someone set mem_limit: 256m and the app actually needs 1 GB.

Case 4: exit code 143 — clean SIGTERM

bash
docker inspect --format '{{.State.ExitCode}}' CONTAINER_NAME
# 143

The process received SIGTERM (typically from docker stop) and shut down. Usually expected. If it's happening without you stopping the container, something else is sending the signal — Compose's down, a parent script, or an external process supervisor.

Run the container in the foreground to see what's happening

The fastest way to diagnose a container that exits is to run it in the foreground:

bash
docker run --rm -it my-image

Without -d, the output streams to your terminal and you can see what's happening in real time. With --rm, the container is gone the moment you ctrl-C — no cleanup needed.

If your image expects -d for some reason (a service that demonizes), override the CMD with a shell:

bash
docker run --rm -it --entrypoint sh my-image
# Then run the original command manually

The "keep it running so I can debug" trick

Sometimes you need a container to stay alive even though its main process has exited, so you can docker exec into it. Override the CMD with tail -f /dev/null:

bash
docker run -d --name dbg --entrypoint tail my-image -f /dev/null
docker exec -it dbg sh
# Now you're inside a running container.
# Manually run the original command, watch what happens.

tail -f /dev/null blocks forever doing nothing — perfect placeholder. (sleep infinity works on coreutils-based images; not on alpine's BusyBox.)

CMD shell form vs exec form

Subtle case worth knowing: your container exits with code 0 even though the process inside is "running." Often caused by shell-form CMD:

dockerfile
CMD npm start

This runs /bin/sh -c "npm start". The shell starts npm, npm starts node — but if there's any signal handling weirdness or if npm's "exit when the script exits" semantics fire wrong, the container can exit despite node being alive.

Use exec form for actual servers:

dockerfile
CMD ["node", "server.js"]

That runs node directly as PID 1, no shell wrapper.

Common pitfalls

  • -d on a container that needs -it. Run interactively first to see what the image actually expects.
  • No logs and exit code 0. Image probably has a CMD that exits cleanly (like bash with no input). Try -it or a different command.
  • Bind mount erased something the image needs. Mounting ./local-dist:/app/dist over an image that had files in /app/dist makes the container behave as if those files were gone — because they are, from the container's perspective.
  • Wrong architecture image on Apple Silicon. "exec format error" then exit. See exec format error.
  • USER running as a user that can't read the working files. Permission errors that look like crashes.

What to do next

FAQ

Sources

Authoritative references this article was fact-checked against.

TagsDockerTroubleshootingContainersDebuggingDevOps

Found this useful? Pass it on.

Copied
Ishan Karunaratne

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

R

Running Docker Containers as a Non-Root User

By default, processes inside Docker containers run as root, which is risky. Switch to a non-root USER, fix permissions on volumes and ports, and configure Compose and Kubernetes to refuse to run root containers.