Containers·8 min read·

Debugging a Container That Won't Start Properly

A methodical path for containers that exit immediately: state, logs, inspect, exit codes, and interactive reproduction that finds the real cause fast.

NB

Netbay Engineering

Netbay Engineering

On this page

A container that exits immediately is a debugging exercise with a fixed answer: something in its startup failed. The frustration comes from the number of things that can fail — a missing file, a wrong working directory, a crash before any logging, a healthcheck that kills the healthy-but-slow boot. The professional approach is a fixed sequence that tames the panic and returns the root cause in minutes: establish the state, read the logs, inspect the metadata, and reproduce the startup by hand. This guide walks that sequence with a real-looking case, and every command below works on any Docker host with default tooling.

The startup-failure ladder docker ps -a state + exit code docker logs what did it print docker inspect error + oom flags run by hand reproduce interactively One rung at a time: each command earns the next

Rung One: Establish the State

The first command answers "did it ever start, and what was the exit code". A container that exited with code 0 killed itself cleanly — often a script that ran and finished, which is a design issue, not a crash. A nonzero exit is a failure, and 137 means the OOM killer ended it.

bash
docker ps -a
docker ps -a --filter "status=exited"
docker run -d --name broken my-app:3.1
docker ps -a --filter "ancestor=my-app:3.1"

The exit code leads the investigation: 0 says "look at the command's purpose", 1 typically says "startup validation failed", and 137 points at memory pressure before anything else.

Rung Two: Read the Logs

docker logs shows whatever the process wrote to stdout and stderr before dying, and it is honest about silence — a container that crashed before its logging initialized prints nothing, which is itself a finding.

bash
docker logs broken
docker logs --tail 100 broken
docker logs broken 2>&1 | head -50

A missing DLL-equivalent (not found), a bind failure (address already in use), or a permission error (permission denied) is almost always right there in the last lines. If stdout is empty, skip straight to inspect and reproduce by hand instead of staring at a blank screen.

Rung Three: Inspect the Metadata

docker inspect is the container's medical record: exit code, error message, restart count, OOM flag, image, entrypoint, and mounts. Grab the exact fields:

bash
docker inspect broken --format 'code={{ .State.ExitCode }} oom={{ .State.OOMKilled }}'
docker inspect broken --format 'error={{ .State.Error }}'
docker inspect broken --format 'restarts={{ .RestartCount }}'
docker images

State.Error capturing the daemon's reported failure, and State.OOMKilled true is a memory diagnosis before a single log line. Cross-reference RestartCount with the exit code: a container caught in restart: unless-stopped loops with a counting exit code is a startup failure cycling, not a flake.

Rung Four: Reproduce the Startup by Hand

When the logs are silent, run the container's own command interactively so you see the error on a terminal instead of guessing from exit codes.

bash
# Override the entrypoint with a shell that stays open
docker run --rm -it --entrypoint sh my-app:3.1
# now inside: try the real command manually
/app/start.sh
/app/server --config /app/config.yaml

# Emulate a fresh run and watch the failure happen live
docker run --rm my-app:3.1

The sequence inside the shell reproduces the container command exactly as it runs at boot, except now the failure prints where you can read it. Missing file, wrong permissions, a config path that does not exist — all visible in seconds. For comparisons, inspect the image to see what the container was actually told to run:

bash
docker image inspect my-app:3.1 --format 'entrypoint={{ .Config.Entrypoint }} cmd={{ .Config.Cmd }} workdir={{ .Config.WorkingDir }}'

Rung Five: The Fix Loop, With the Clock Running

With the root cause named, the fix falls into a shape: correct the file (config path, missing mount), correct the command (entrypoint, working directory), or correct the resource (memory limit, port conflict). Mounting a host file over a missing config, or recreating with the right -p, resolves most cases. The loop is cheap — docker logs, a one-line change, docker rm and docker run again — and staying on the ladder rungs keeps it that way.

bash
docker stop broken && docker rm broken
docker run -d --name fixed -v /srv/app/config.yaml:/app/config.yaml:ro   -p 8080:8080 my-app:3.1
docker ps && docker logs --tail 20 fixed

Ten seconds between failure and the next attempt is what the sequence buys you. Once fixed, screenshot-worthy wisdom belongs in a systemd unit or compose file so the fix does not live in someone's terminal history.

Takeaway

For a container that will not start, the method beats the memory: ps -a hands you an exit code, logs hand you the last words, inspect hands you the metadata, and an interactive run hands you the actual failure before you change anything. The entire loop is five commands, and the answer to "why won't this start" is always at one of the rungs. On a Netbay VPS you control the whole Docker host, so the ladder above is available in full from the first container — debug your way to a fix at netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance