Diagnosing Containers That Exit Immediately on Startup
A container exits right after docker compose up. Read exit codes 0, 1, and 137, inspect logs and PID 1, fix missing CMD and OOM kills, and prevent recurrence.
You run docker compose up and a service flips to Exited (0), Exited (1), or Exited (137) within a second, before logging anything useful — one of the most common local failure points blocking onboarding.
Diagnostic
List containers including stopped ones and read the exit code, then pull the last log lines.
#!/usr/bin/env bash
set -euo pipefail
docker compose ps -a
docker compose logs --tail=50 app
docker inspect --format '{{.State.ExitCode}} {{.State.OOMKilled}} {{.State.Error}}' "$(docker compose ps -aq app)"
Expected BAD output — the service is gone within a second and the exit code tells you which class of failure occurred:
NAME IMAGE STATUS PORTS
app-app-1 app:local Exited (137) 2 seconds ago
137 true
An exit 0 means the main process finished (no long-running command). Exit 1 (or 2, 126, 127) means it crashed or the entrypoint was unrunnable. Exit 137 with OOMKilled true means the kernel killed it for exceeding its memory limit (128 + signal 9).
Root cause
A container lives exactly as long as its PID 1 process. If the image has no long-running CMD, PID 1 exits immediately and the container stops with code 0 — common when a base image's command is overridden to something like bash with no script, or a shell entrypoint reaches its end. Code 1/127 means PID 1 itself failed to run: a missing binary, a command not found, a script without execute permission, or a wrong shebang. Code 137 is distinct — the process started fine but the cgroup memory limit (or Docker Desktop's VM allocation) was too low, so the OOM killer reaped it. Because PID 1 dies before the app logs anything, the logs are empty and the only signal is the exit code and OOMKilled flag.
Resolution
- Read the exit code to pick the branch (0 = no long process, 1/127 = bad command, 137 = OOM).
- For exit 0, ensure a foreground, long-running
CMDand that the entrypoint does not background the app. - For exit 1/127, run the image interactively to reproduce the failing command directly.
- For exit 137, raise the memory limit (and Docker Desktop's VM memory) and re-check.
For exit 0 — give PID 1 a foreground process and stop backgrounding it:
# Dockerfile — run the server in the foreground as PID 1
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci --omit=dev
# WRONG: `node server.js &` backgrounds it and the shell exits -> Exited (0)
CMD ["node", "server.js"]
For exit 1/127 — reproduce interactively, bypassing the entrypoint:
#!/usr/bin/env bash
set -euo pipefail
# Drop into a shell to find the missing binary / bad shebang / permission bit.
docker compose run --rm --entrypoint sh app -c 'ls -l /app/entrypoint.sh; head -1 /app/entrypoint.sh; which node'
For exit 137 — set an explicit limit and confirm it fits the workload:
# docker-compose.yml
services:
app:
build: .
deploy:
resources:
limits:
memory: 512M
mem_swappiness: 0
Expected output
After fixing PID 1, the service stays up and reports a running, healthy state:
$ docker compose ps
NAME IMAGE STATUS PORTS
app-app-1 app:local Up 30 seconds (healthy) 0.0.0.0:3000->3000/tcp
Inspecting a recovered container shows a zero exit code is no longer being hit and OOM is false:
$ docker inspect --format '{{.State.Status}} {{.State.OOMKilled}}' app-app-1
running false
Prevention
- Add a Compose
healthcheckso a process that starts but immediately dies is reported asunhealthyrather than silently restarting — see resolving service startup-order and healthcheck races. - Run
make doctorbeforeupso memory and tool preconditions are checked — see building an onboarding health-check script. - Pin a foreground
CMDin the image and never background the app in the entrypoint.
macOS / Windows (Docker Desktop): exit 137 is frequently the VM's memory ceiling, not the container's. Raise Resources > Memory in Docker Desktop before lowering suspicion of a leak. WSL2: the WSL VM has its own memory cap; set
[wsl2] memory=8GBin%UserProfile%\.wslconfigand runwsl --shutdownto apply, or large builds OOM at 137. Apple Silicon (ARM64): an image without an arm64 manifest exits with code 1 andexec /bin/sh: exec format error; addplatform: linux/amd64to the service.
Rollback
#!/usr/bin/env bash
set -euo pipefail
docker compose down && git checkout -- Dockerfile docker-compose.yml # revert image/limit edits