Skip to main content

Docker · troubleshooting

Container exits immediately

The container starts, the process finishes, and Docker stops the container — because a container lives exactly as long as its main process. The exit code tells you which of two quite different situations you are in.

Run this first

step 1 of 3
docker ps -a --filter name=<container> --format '{{.Status}}'

Look for `Exited (0)` means nothing failed — the process finished. Any non-zero code means it tried and could not.

The exit code splits this into two different investigations, so it is the first thing to establish.

Work out which cause you have

A few questions to narrow the list. Every answer ends in a command that confirms or rules the cause out — this cannot see your cluster, so nothing here is a certainty until you have checked.

Narrow it down

0 answered · nothing is sent anywhere

What exit code does `docker ps -a` report?

This is the branch point — 0 and non-zero lead to almost unrelated causes.

Cause space

5 of 5 still possible

  • The main process was never going to stay runningCommon
  • Required configuration was absent, so the process exited on start-upCommon
  • The image was built for a different CPU architectureOccasional
  • ENTRYPOINT and CMD combined into something that is not a commandOccasional
  • The container was killed for exceeding its memory limit during start-upRare

Nothing ruled out yet. Answer the question above and the branches your answer eliminates will strike through here.

Check it with a tool

Or diagnose it manually

In this order. The first command usually contains the whole answer.

  1. Step 1

    docker ps -a --filter name=<container> --format '{{.Status}}'

    The exit code splits this into two different investigations, so it is the first thing to establish.

    Look for `Exited (0)` means nothing failed — the process finished. Any non-zero code means it tried and could not.

  2. Step 2

    docker logs <container> 2>&1 | tail -40

    For a non-zero exit, the application usually said why. For exit 0, the emptiness is itself the answer.

    Look for A named missing variable, a connection refusal, or `exec format error`.

  3. Step 3

    docker inspect <container> --format 'code={{.State.ExitCode}} oom={{.State.OOMKilled}} cmd={{json .Config.Cmd}} ep={{json .Config.Entrypoint}}'

    Everything else in one line: the code, whether the kernel killed it, and what it was actually asked to run.

    Look for An entrypoint and command that concatenate into something sensible, and oom=false.

Every cause, and how to fix it

Ordered by how often each one turns out to be the answer.

The main process was never going to stay running

Common

The command completed and returned, so the container had nothing left to do. Classic versions: a base image whose default command is a shell that immediately reaches end-of-input, a script that ends, or a server launched with a trailing `&` so the foreground shell exits while the server is still starting.

Confirm

docker inspect <container> --format '{{.State.ExitCode}} {{json .Config.Cmd}} {{json .Config.Entrypoint}}'

Exit code 0 alongside a command that is not a long-running server. If Entrypoint is null and Cmd is something like ["bash"], that is the whole answer.

Fix

  • Run the actual server in the foreground as the container's main process. A container does not need a process manager; the application *is* the process.
  • If you genuinely need the container to stay up for debugging, run it interactively with `-it` so the shell has a terminal to read from.
  • Remove any trailing `&` from the start command. Backgrounding the server means the foreground command finishes, which ends the container.
# Foreground, in exec form
CMD ["node", "server.js"]

# Or, to keep a shell alive for poking around:
docker run -it --entrypoint sh myimage

Exec form also matters for signal handling — see ENTRYPOINT vs CMD.

Required configuration was absent, so the process exited on start-up

Common

The application read its configuration, found something missing — a database URL, an API key, a required environment variable — and exited rather than starting in an unusable state. This is correct behaviour and it looks like a crash.

Confirm

docker logs <container> 2>&1 | tail -30

An explicit message near the end naming what was missing. Most frameworks say so plainly; the difficulty is that people check `docker ps` rather than the logs.

Fix

  • Pass the missing values with `-e` or `--env-file`, or mount the config the application expects.
  • Check for a name mismatch rather than an absence — a variable spelled `DATABASE_URL` in compose and read as `DB_URL` in code produces exactly this.
  • Do not bake secrets into the image to make this go away. Inject them at run time.

The image was built for a different CPU architecture

Occasional

An image built on an arm64 machine and run on amd64, or the reverse. The container is created and the binary cannot execute, so it fails almost instantly with an exec-format error.

Confirm

docker logs <container> 2>&1 | head -5; docker image inspect <image> --format '{{.Architecture}}'

`exec format error` in the logs, or an image Architecture that differs from your host's. This became common as arm laptops became normal.

Fix

  • Build for the target platform explicitly with `docker build --platform linux/amd64`.
  • For images used across both, build a multi-platform image rather than one per developer.

ENTRYPOINT and CMD combined into something that is not a command

Occasional

Docker concatenates ENTRYPOINT and CMD. When one is set as a full command and the other adds unexpected arguments, the result can be a valid string that is not a valid invocation — so the process fails immediately with a usage error or a not-found.

Confirm

docker inspect <image> --format 'entrypoint={{json .Config.Entrypoint}} cmd={{json .Config.Cmd}}'

Read the two as one line concatenated in that order. If the combined result is not something you would type, that is the fault.

Fix

  • Put the executable in ENTRYPOINT and only its default arguments in CMD.
  • Use exec form — the bracket syntax — for both. Shell form introduces `/bin/sh -c`, which changes both parsing and signal handling.

The container was killed for exceeding its memory limit during start-up

Rare

A memory limit lower than the application's start-up footprint. The kernel kills the process before it finishes initialising, so it looks like an immediate exit rather than a resource problem.

Confirm

docker inspect <container> --format '{{.State.ExitCode}} oom={{.State.OOMKilled}}'

`oom=true`, or exit code 137. Note that a 137 with `oom=false` means something sent SIGKILL for another reason.

Fix

  • Raise the limit above the observed start-up peak, which is often higher than steady-state usage.
  • Check whether the runtime is sizing its heap against the host rather than the container — a JVM or Node process that has not been told the limit will happily exceed it.

Understanding it properly

Skip this if you are mid-incident — the working part of the page is above. Worth reading afterwards, because understanding the mechanism is what stops the next one.

What is actually happening

A container is not a machine that stays up. It is a process, and when that process returns, there is nothing left to run. Docker is not stopping your container; your container finished.

So the useful question is never "why did Docker stop it". It is "what did PID 1 do, and why did it return" — and the exit code splits that into two almost unrelated investigations.

Exit code 0 means the process completed successfully and had nothing more to do. That is usually a command that was never going to stay running: a shell with no input, a one-shot script, a server started in the background so the foreground command returned. Nothing failed.

A non-zero exit code means the process tried to run and could not. Now the logs matter, because the application had something to say before it gave up.

How to tell this is your problem
WhereWhat you see
docker psThe container is absent. It never appears, because it is no longer running.
docker ps -aSTATUS reads `Exited (0)` or `Exited (1)` a few seconds after creation.
docker logs <container>Either empty (exit 0, nothing to say) or the application's own error output.

How to know it is actually fixed

  • `docker ps` shows the container running rather than absent, and it is still there a minute later.
  • `docker logs` shows the application's normal start-up output reaching a listening or ready state.
  • The container survives a `docker restart`, which rules out a first-run-only condition.

Stopping it happening again

  • Add a HEALTHCHECK that exercises the application rather than the port, so a container that starts but cannot serve is distinguishable from one that works.
  • Fail fast and loudly on missing configuration, naming the variable. An application that exits silently converts a one-minute fix into a debugging session.
  • Use exec form for ENTRYPOINT and CMD, which avoids both the concatenation surprises and the signal-handling problem.

Did this get you to an answer?

No text box on purpose — please do not paste production logs anywhere

Sources

Behaviour described here is drawn from official documentation. Where a figure could not be confirmed on an official page it is attributed in the text rather than stated as canonical.

Related

Container exits immediately — causes, diagnosis and fix | DevOps Insights