Container Exit Codes
A single small number left behind when a container stops — and often the fastest, most reliable clue to why it stopped at all, if you know how to read it.
When a Docker container stops, it does not just quietly disappear — it leaves behind a single number, its exit code, recorded alongside it. That number is one of the most underused diagnostic tools in everyday container work. Most people notice a container stopped, glance at the logs, and move on without ever checking the exit code itself, even though that number often narrows the cause down immediately: a normal, intentional shutdown looks completely different from a crash, which looks completely different from Docker forcibly killing the container because it ran out of memory. Learning to read exit codes is a small skill with an outsized return, because it turns “the container just stopped” into a specific, actionable starting point for investigation. This guide covers where the exit code actually comes from, what the most common values mean, and how to design applications and restart policies that make good use of them.
ACore Concepts
What an exit code is, generally
An exit code is a small number, from 0 to 255, that a process returns to the operating system the moment it finishes running, whether that finish was planned or not. It has been a standard part of how programs communicate their outcome since long before containers existed — a program exits with 0 to signal success, and with some non-zero value to signal that something went wrong, with the specific number often hinting at what.
Where a container’s exit code actually comes from
A Docker container’s exit code is simply the exit code of its main process — the one designated to run as process ID 1 inside the container. When that process finishes, whatever code it returns becomes the container’s own recorded exit code, and Docker considers the container stopped at that point.
Why this single number is so useful
Because the exit code is recorded automatically by Docker regardless of whether anyone was watching when the container stopped, it becomes a durable, always-available first clue — visible through basic container inspection — long after the moment of failure has passed and the terminal window where it happened is gone.
Think of an exit code like the short note a delivery driver leaves when they cannot complete a drop-off: “left at door,” “no answer,” “address not found,” “package damaged.” A single short code on a slip attached to the door tells you immediately what class of problem occurred, without requiring you to call the driver and get the full story. It is not the whole explanation, but it points you toward the right question to ask next.
BArchitecture & Components
The container’s main process
The process Docker starts and treats as the container’s identity — its exit code becomes the container’s exit code, regardless of what other processes might be running alongside it.
The number itself
A value from 0 to 255, recorded by Docker the moment the main process finishes, whether through a normal return, an explicit exit call, or being terminated by a signal.
How Docker asks a process to stop
Notifications sent to the main process when Docker wants a container to stop — typically a polite request first, followed by a forceful one if the process does not respond in time.
Where the code is stored
Part of a stopped container’s recorded state, viewable through standard container inspection tools long after the container has stopped running.
What happens next
A container-level setting that can automatically restart a stopped container based on how it exited — for example, only restarting on failure rather than every time.
What larger systems do with the code
Orchestration platforms commonly use the exit code, alongside health signals, to decide whether to restart, replace, or flag a container as persistently failing.
CInternal Working
The clean, expected codes
An exit code of 0 means the main process finished on its own and considered its work successful — a normal, planned stop. Any other single-digit code the application itself chose to return, most commonly 1, generally means the application encountered an error it detected and decided to stop over, though the exact meaning of that specific number is entirely up to whoever wrote the application.
The codes that mean “something outside the process intervened”
When a process is terminated by a signal rather than exiting on its own, Docker reports that as 128 plus the signal’s number. The two that come up constantly in container work are 137, which is 128 plus signal 9 (a forceful, immediate kill) — most commonly seen when a container is killed for exceeding its memory limit — and 143, which is 128 plus signal 15 (a polite request to terminate), commonly seen during an ordinary, intentional stop where the process was given a chance to shut down gracefully and did.
The codes that mean Docker itself could not even start the process
Codes like 126 and 127 generally indicate a problem before the application’s own logic ever ran at all — commonly, that the command specified could not be executed or could not be found. These point toward a configuration or image problem, not a bug inside the running application.
An exit code chosen by the application itself tells you the application detected a problem and reported it. An exit code produced by a signal (128 plus the signal number) tells you something external — Docker, the operating system, an out-of-memory condition — ended the process, which is a meaningfully different kind of failure to investigate.
DData Flow & Lifecycle
flowchart TD
A["Container's main process is running"] --> B{"How does it stop?"}
B -->|"Finishes normally, no error"| C["Process exits with code 0"]
B -->|"Detects an internal error, exits itself"| D["Process exits with code 1 or another app-chosen code"]
B -->|"docker stop sends SIGTERM, process shuts down gracefully"| E["Process exits with code 143"]
B -->|"Exceeds memory limit, kernel sends SIGKILL"| F["Process killed, exit code 137"]
B -->|"Command in image cannot be found or executed"| G["Container never truly starts, exit code 127 or 126"]
C --> H["Docker records exit code on the stopped container"]
D --> H
E --> H
F --> H
G --> H
H --> I["Restart policy or orchestrator reads the code and decides what happens next"]
Fig. 1 — Very different real-world events all funnel into the same small, recorded number, which is exactly what makes reading that number a fast first diagnostic step.
Notice that the exit code is recorded the same way regardless of which path produced it — Docker does not need anyone to explain what happened; the number is already sitting there, ready to be read with a basic inspection command, whether the investigation happens the instant the container stops or hours later.
Combining exit codes with logs for a full picture
The exit code narrows down the category of failure quickly, but it rarely tells the complete story on its own — pairing it with the container’s logs from just before it stopped usually turns “it exited with 1” into a clear understanding of exactly what went wrong and why.
EAdvantages, Disadvantages & Trade-offs
Strengths
- Always recorded automatically, with no extra logging or instrumentation required to get a first signal of what happened
- A small, standardized numeric range that tools, scripts, and orchestrators can act on programmatically without parsing free-text logs
- Distinguishes at a glance between a self-reported application failure and an externally imposed termination like an out-of-memory kill
Limitations
- A single number cannot convey the full detail of what went wrong — it is a starting point, not a complete explanation
- Applications that are careless about their own exit codes (always returning 0, or always returning 1 regardless of the real cause) make this signal far less useful
- The same numeric code can, in principle, mean different things across different applications unless a consistent convention is deliberately followed
The trade-off in one sentence: exit codes are an extremely cheap, always-available diagnostic signal, but their real value depends entirely on whether the application producing them actually reports meaningful, distinct codes in the first place.
FSecurity
An application that repeatedly crashes and restarts in a tight loop — commonly triggered by a persistent error condition that a restart never actually fixes — can itself become a resource and availability concern, consuming CPU and memory on constant restart attempts and generating a flood of log entries that can obscure other, more important signals. Watching exit codes over time, not just once, is what turns a single crash into a recognized crash loop before it becomes a bigger operational problem.
A restart policy configured to always restart a failed container, with no limit, can turn a single misconfiguration or a genuinely broken deployment into an endless, resource-consuming restart loop instead of a clear, visible failure someone can investigate and fix — effectively masking the problem while still consuming resources the whole time.
A sound approach pairs restart policies with reasonable limits or backoff behavior, and treats a container that keeps exiting with the same non-zero or signal-based code as a signal worth alerting on, rather than something to be silently and endlessly restarted in the background.
GDesign Patterns & Anti-patterns
Pattern
Using distinct, documented exit codes for different known failure categories inside an application — one code for a configuration problem, a different one for a failed dependency connection, and so on.
Why it works
It lets anyone investigating a stopped container narrow down the cause immediately from the exit code alone, before even opening the logs, which is especially valuable when triaging many containers at once.
Pattern
Handling termination signals gracefully inside the application, so a normal stop request produces a clean, expected exit code rather than being forcefully killed after a timeout.
Why it works
It keeps ordinary, intentional shutdowns clearly distinguishable from actual problems, and avoids the confusion of seeing a forceful-kill exit code during what was really just a routine, expected stop.
Anti-pattern
Writing application code that always exits with 0, even when an internal error occurred, because it “handled” the error by logging it and quietly stopping.
Why it causes problems
It throws away the single most useful signal available for automated monitoring and restart decisions, making a genuine failure look identical to a normal, successful stop to anything watching the exit code.
Anti-pattern
Using exit code 1 as a catch-all for every possible error condition, with no distinction between different underlying causes.
Why it causes problems
It reduces the exit code back down to a bare “something went wrong” signal, losing most of the diagnostic value a more deliberate set of codes would have provided at essentially no extra cost.
HBest Practices & Common Mistakes
Check the exit code before diving into logs
A quick glance at the exit code often points investigation in the right direction before reading a single line of log output.
Recognize 137 and 143 immediately
These two signal-based codes come up constantly — 137 usually points toward an out-of-memory kill, 143 usually points toward a normal, requested stop.
Design meaningful, distinct exit codes for critical applications
Especially for services that will run unattended, deliberate exit codes pay for themselves the first time something goes wrong at an inconvenient hour.
Ignoring exit codes in automated monitoring
Monitoring that only watches whether a container is running, without ever checking how or why it last stopped, misses an easy, already-available signal.
Assuming 126 or 127 means the application itself is broken
These codes generally point to a problem before the application ever ran — a missing command, a permissions issue, an image configuration mistake — not a bug in the application’s own logic.
Letting restart policies run unbounded on a persistently broken container
Without some limit or alerting, an unlimited restart policy can hide a real, ongoing failure behind an endless, resource-consuming restart cycle.
IReal-World & Industry Examples
Out-of-memory kills in production
Teams running memory-constrained containers regularly encounter exit code 137 during load spikes, which is often the very first clue pointing toward a memory limit that needs adjusting or a memory leak that needs investigating.
Kubernetes crash-loop detection
Kubernetes explicitly tracks repeated container failures and surfaces a distinct, well-known status for a container that keeps exiting and restarting, using the pattern of repeated non-zero exit codes as a core part of that detection.
CI/CD pipeline steps
Continuous integration systems rely directly on a container’s exit code to decide whether a build or test step passed or failed, making a consistent, meaningful exit code essential for pipelines built around containerized test runners.
Graceful shutdown handling in application frameworks
Many modern application frameworks now include built-in handling for termination signals specifically so that a normal deployment rollout produces clean, expected exit codes across every container being replaced, rather than a wave of forceful, signal-based kills.
JFrequently Asked Questions
KSummary and Key Takeaways
The essentials, in one place
- A container’s exit code is its main process’s exit code, recorded automatically the instant the container stops.
- 0 means success, and other application-chosen codes signal a self-detected failure.
- 128 plus a signal number means something external ended the process — 137 for a forceful kill (often out-of-memory), 143 for a polite, requested stop.
- Codes like 126 and 127 point to a problem before the application ever ran, not a bug in its own logic.
- Exit codes are a cheap, always-available first diagnostic signal, but they work best paired with logs for the full picture.
- Meaningful, distinct exit codes are a design choice, and applications that always return 0 or always return 1 throw that value away.
- Restart policies should respect exit codes thoughtfully, with limits or alerting, rather than restarting endlessly regardless of the cause.