Updated

Docker exit codes explained: why your container exits right after deploy

CI says the deploy succeeded, but a few seconds later docker ps -a shows Exited (1) or Exited (137), or the container keeps flipping to Restarting. Users can't reach the app. This guide is for developers and founders who just shipped to a VPS and want a first read of the Docker exit code: confirm the container's state, look up what the code usually means and what it does not prove, then read a bounded window of logs to find the first error. For a container stuck in a restart loop, continue with Docker container keeps restarting once you have the exit code.

Every command here is read-only. Run them only on a server you are authorized to access, and replace YOUR_CONTAINER with the real container name or ID.

1. Confirm the state and the exit code

docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'
docker inspect --format '{{.State.ExitCode}} {{.State.OOMKilled}} {{.State.Error}}' YOUR_CONTAINER

If you deploy with Compose, docker compose ps -a lists the project's containers, including stopped ones; the container name is usually <project>-<service>-1.

To see how long the process actually ran and whether a restart policy is involved:

docker inspect --format 'started={{.State.StartedAt}} finished={{.State.FinishedAt}} restarts={{.RestartCount}} policy={{.HostConfig.RestartPolicy.Name}}' YOUR_CONTAINER

A few seconds between started and finished is the "exits right after deploy" pattern. These timestamps are in UTC (they end in Z); convert before comparing them with your deploy time or with host logs in local time.

What this does not tell you: why the process exited. It also doesn't confirm you're looking at the right thing, so check that the name and image tag match the deploy you just shipped.

2. Docker exit code table: what to run next, and what each code does not prove

CodeUsual meaningWhat to run nextWhat it does not prove
0The main process finished successfullyCheck what the container is told to run (command below). A web service that exits 0 usually started itself in the background, or its command was a one-shot task. For a migration or backup job, 0 is the expected result.That anything is wrong with your code, or that the service is fine. It only says the main process ended without an error.
1Generic application errorRead the logs (step 3) and find the first error. Compare with what changed in this deploy: image tag, environment variables, config, migrations.Which error it was. Many runtimes exit 1 for any uncaught exception or failed startup check.
125Docker itself failed: the docker run command or the daemon rejected the requestRead the error printed by docker run / docker compose up in the CI or deploy log, and State.Error. Typical messages: unknown flag, image pull denied, port already allocated (then sudo ss -ltnp shows who holds the port).An application bug. Usually your process never started at all.
126The command was found but couldn't be executedCheck the configured command and the file's permissions and format inside the image (commands below). A script on a bind mount keeps the host file's permissions, so a missing execute bit on the host shows up here.That the file is missing. 126 is "can't execute", 127 is "can't find".
127The command couldn't be foundSame command check. Confirm the binary exists in this image and on its PATH; a slimmer base image or a typo in Compose command: is a common cause. A shell inside the container also exits 127 when a command in a script isn't found.A crash after startup. The program you meant to run never started.
137Killed with SIGKILL (128 + 9)Look at OOMKilled from step 1. true: memory is the lead hypothesis; check the memory limit and kernel log (commands below). false: look for who sent the kill with docker events: docker kill, or a docker stop that waited out its grace period (default 10 s) because the app ignored SIGTERM.Out of memory. 137 alone is not OOM. Even OOMKilled=true should be matched against timestamps in host memory and kernel logs. More in the exit code 137 guide.
139Killed with SIGSEGV (128 + 11), a segmentation faultLogs around the exit, then the kernel log for a segfault line naming the binary or library. Note any native dependency, base image or runtime upgrade in this deploy.Where the bug is. It tells you the process touched invalid memory, not which component or why.
143Stopped with SIGTERM (128 + 15)Find out who asked it to stop: a redeploy, docker stop, docker compose down/up recreating it, a host shutdown, or an orchestrator. docker events and your deploy timeline usually answer this.A crash. 143 is a normal stop request; the real question is who sent it and whether the replacement container came up.

125, 126 and 127 can come from two places. When Docker couldn't start the process, they are the exit status of the docker run command itself: you see them in the CI or deploy log, the container usually stays Created, and State.Error holds the message (after a 125, the container's own ExitCode in docker inspect may show a different value, such as 128). When a shell or entrypoint script inside a running container hits the same problem, the container itself exits with 126 or 127 and the message is in docker logs.

Codes above 128 generally mean "killed by signal (code − 128)". kill -l 137 in bash prints KILL; try it for any code you don't recognize. Any other number is whatever the application chose to return, so its logs or documentation decide what it means.

Commands referenced in the table

Check what the container runs (codes 0, 126, 127):

docker inspect --format 'entrypoint={{json .Config.Entrypoint}} cmd={{json .Config.Cmd}}' YOUR_CONTAINER
docker image inspect --format '{{.Os}}/{{.Architecture}}' YOUR_IMAGE
uname -m

The second and third lines compare the image's platform with the host's (for example linux/arm64 against x86_64). A mismatch usually shows up in the logs as exec format error.

Memory and signals (codes 137, 139, 143). docker events only returns recent events the daemon still holds (the last 256), so run it soon after the incident:

docker inspect --format 'oom={{.State.OOMKilled}} memory_limit_bytes={{.HostConfig.Memory}}' YOUR_CONTAINER
docker events --since 1h --until "$(date +%s)" --filter container=YOUR_CONTAINER
free -h
sudo dmesg -T | grep -i -E 'out of memory|killed process|segfault' | tail -n 20

What these do not tell you: free and docker stats show the present, not the peak at the moment of the kill, and a stopped container usually has no stats at all. Depending on the setup, an OOM kill decided at host level may not set Docker's OOMKilled flag.

3. Read a bounded log window and find the first error

docker logs --since 30m --timestamps YOUR_CONTAINER 2>&1 | head -n 80
docker logs --since 30m --timestamps YOUR_CONTAINER 2>&1 | grep -n -i -E 'error|fatal|panic|exception|denied|not found|refused' | head -n 20

First errors that come up often (each is still a hypothesis until you check it):

Before you paste logs into a chat, a ticket or an AI prompt, redact passwords, tokens, connection strings, customer emails and internal addresses.

Doing this in OpsMate

OpsMate puts an SSH terminal and an AI assistant on the same server page. You can type the docker ps, docker inspect and docker logs commands above yourself, which is usually fastest for the exit code. You can also describe the problem in plain language, for example "find out why YOUR_CONTAINER exits after the deploy and summarize the first errors in its logs". The AI proposes troubleshooting commands, and beyond logs it can use docker, journalctl, ps, df and ss for checks. After a command has run, click Analyze to get a conclusion; the commands and output stay in the terminal so you can check it against the raw lines. When you click Analyze, the command output is sent to cloud AI for analysis; what the desktop app keeps on your own machine by default is your SSH credentials. If the logs contain customer data or secrets, run the commands yourself and share only a redacted excerpt.

Boundaries

Illustrative example

Illustrative example (not a real customer case): after switching the image to a slimmer base, docker ps -a shows Restarting (127) 4 seconds ago. docker inspect prints 127 false with an empty error, so Docker started the process and it's not a memory kill. started and finished are about a second apart. The first line of docker logs --since 15m --timestamps reads /app/start.sh: 5: exec: gunicorn: not found: the entrypoint script runs, but the new base image doesn't include the app server. Changing the restart policy or adding memory would not help. The next step is a decision for a person: roll back to the previous image tag, or rebuild with the dependency installed, and then watch whether the container stays up.

AI diagnostic prompt

A container on this server exits a few seconds after deploy. Without changing anything, get its State.ExitCode, OOMKilled, Error, StartedAt, FinishedAt and restart count, summarize the first errors in its logs since the deploy with timestamps, and tell me what that exit code usually means and what it does not prove. Separate confirmed facts from hypotheses and list what is missing. Do not restart or remove containers, change restart policies, edit Compose files or change memory limits. If a change is needed, propose the smallest step with its risks, how to verify it and how to roll it back, and wait for my approval.

Next checks

Try OpsMate

500 free AI calls per month and unlimited servers. The desktop app keeps your SSH credentials on your own machine by default.

Need help interpreting the evidence?

OpsMate helps developers and operators investigate with AI. Review the evidence. After you click Analyze, the command output is sent to cloud AI for analysis; redact sensitive information first.

Start free Desktop with local credentials

Troubleshooting guides