Home DevOps Docker Troubleshooting Guide: Diagnose and Fix Container Crashes in Production
Intermediate 7 min · July 11, 2026

Docker Troubleshooting Guide: Diagnose and Fix Container Crashes in Production

Docker troubleshooting guide for production container crashes.

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.

Follow
Production
production tested
July 18, 2026
last updated
2,466
articles · all by Naren
Before you start⏱ 30 min
  • Basic Docker commands (run, ps, logs, exec)
  • Familiarity with Linux processes and filesystem
  • Access to a Docker host with production containers
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer

When a container crashes, start with docker logs for the exit message, then docker inspect for exit code and resource limits. For OOM, check dmesg | grep -i oom. For disk, docker system df. For zombie processes, ps aux inside the host.

✦ Definition~90s read
What is Docker Troubleshooting?

Docker troubleshooting is the systematic process of identifying and resolving container failures in production. It covers crashes, hangs, resource exhaustion, and networking issues using Docker-native tools and Linux internals.

Think of a container like a shipping container on a cargo ship.
Plain-English First

Think of a container like a shipping container on a cargo ship. If the container falls off (crashes), you check the manifest (logs) to see why. Maybe it was overloaded (OOM), or the ship's crane broke (host issue). Docker troubleshooting is the process of reading the manifest, checking the ship's logs, and inspecting the container's condition to figure out what went wrong.

Your container just died at 3 AM. No alert, just a 137 exit code. Now what? Most devs panic and restart — then it dies again. That's not troubleshooting, that's gambling.

The problem is that Docker abstracts away the OS, but it doesn't abstract away failures. Containers crash for the same reasons processes crash: out of memory, disk full, deadlocks, or misconfigured limits. The difference is you have fewer tools inside the container to debug.

By the end of this guide, you'll be able to diagnose any container crash in under 5 minutes. You'll know exactly which command to run, what to look for, and how to fix it permanently. No more guessing, no more restart loops.

Why Containers Crash: The Three Horsemen

Before you run any command, understand the three root causes of container failure: resource exhaustion (OOM, disk full), application bugs (segfaults, uncaught exceptions), and infrastructure issues (Docker daemon restart, network timeouts). Each leaves a different signature.

OOM kills leave exit code 137 and a kernel log. Disk full leaves exit code 1 with a 'no space left on device' error in logs. Segfaults leave exit code 139. Infrastructure issues often leave exit code 125 (daemon error) or 126 (command not executable).

Your first step is always: check the exit code. docker inspect --format '{{.State.ExitCode}}'. Then match it to the list above. This narrows your search from everything to one category.

check_exit_code.shBASH
1
2
3
4
5
6
7
8
#!/bin/bash
# io.thecodeforge — DevOps tutorial
# Check exit code of a stopped container
docker inspect my_app --format '{{.State.ExitCode}}'
# Output: 137
# Then check OOM logs
dmesg | grep -i oom | tail -5
# Output: [12345.678] oom-kill: constraint=CONSTRAINT_MEMCG ...
Output
137
[12345.678] oom-kill: constraint=CONSTRAINT_MEMCG ...
⚠ Production Trap:
Never trust docker logs alone for crash diagnosis. If the container was OOM-killed, logs may be empty because the kernel killed it before it could write. Always cross-reference with dmesg and exit code.
docker-troubleshooting-guide THECODEFORGE.IO Container Crash Root Cause Layers Layered view of failure domains from kernel to application Application Layer Health Check Failure | Zombie Process | DNS Timeout Container Runtime PID 1 Problem | OOM Kill | Disk Full cgroups v2 Memory Limit | CPU Throttle | IOPS Limit Kernel Out-of-Memory Killer | Filesystem Full | Network Stack Infrastructure Node Disk Pressure | Network Latency | Resource Exhaustion THECODEFORGE.IO
thecodeforge.io
Docker Troubleshooting Guide

Memory Troubleshooting: Beyond the Heap

Memory issues are the #1 cause of container crashes in production. But most devs only look at the app heap. That's like checking your car's fuel gauge when the engine seizes — you're missing the oil pressure.

Inside a container, memory is used by: the app heap, native code (JNI, C libraries), thread stacks, Metaspace (JVM), page cache, and tmpfs mounts. All of it counts against the memory limit. If the sum exceeds the limit, the kernel kills the container.

To see real memory usage: docker stats <container>. Look at the MEM USAGE / LIMIT column. If it's close to 100%, you're on the edge. Then use docker exec <container> cat /sys/fs/cgroup/memory/memory.usage_in_bytes to get the exact cgroup value.

For JVM apps, add -XX:NativeMemoryTracking=summary and use jcmd <pid> VM.native_memory to see non-heap usage. For Node.js, check process.memoryUsage().

memory_debug.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
# io.thecodeforge — DevOps tutorial
# Check memory usage inside container
docker stats my_app --no-stream
# Output:
# CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT   MEM %
# abc123         my_app    45.2%     3.8GiB / 4GiB       95%
# Check cgroup memory
docker exec my_app cat /sys/fs/cgroup/memory/memory.usage_in_bytes
# Output: 4080218931 (about 3.8GiB)
# Check OOM kills for this container
dmesg | grep -i 'my_app' | grep -i oom
# Output: (if any OOM kill happened)
Output
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM %
abc123 my_app 45.2% 3.8GiB / 4GiB 95%
4080218931
💡Senior Shortcut:
Set --memory-reservation to 70% of your memory limit. This tells the kernel to try to keep usage below that value. It gives you a buffer before the OOM killer activates. Also set --kernel-memory to limit kernel memory separately.

Disk Full: The Silent Killer

A full disk doesn't crash your container — it freezes it. Writes fail silently, logs stop, and health checks time out. Then your orchestrator kills the container for being unhealthy. The real fix isn't restarting; it's freeing space.

First, check disk usage on the host: df -h. If any mount is at 100%, that's your problem. Docker stores images, containers, and volumes under /var/lib/docker by default. Use docker system df to see how much space each component uses.

Common culprits: unused images (docker image prune -a), stopped containers (docker container prune), and dangling volumes (docker volume prune). For production, set up a cron job to run docker system prune -f --volumes weekly.

But sometimes the issue is inside the container — logs growing unbounded. Always mount a log directory to a dedicated volume with a size limit, or use a log shipper that rotates.

disk_debug.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
# io.thecodeforge — DevOps tutorial
# Check disk usage on host
df -h /var/lib/docker
# Output:
# Filesystem      Size  Used Avail Use% Mounted on
# /dev/sda1        50G   48G  2.0G  96% /var/lib/docker
# Check Docker disk usage
docker system df
# Output:
# TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
# Images          10        5         20GB      10GB (50%)
# Containers      8         3         5GB       2GB (40%)
# Local Volumes   5         2         10GB      8GB (80%)
# Build Cache     0         0         0B        0B
# Prune unused resources
docker system prune -a --volumes -f
Output
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 50G 48G 2.0G 96% /var/lib/docker
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 10 5 20GB 10GB (50%)
Containers 8 3 5GB 2GB (40%)
Local Volumes 5 2 10GB 8GB (80%)
Build Cache 0 0 0B 0B
⚠ Never Do This:
Don't run docker system prune -a in production without checking what it removes. It deletes all unused images, including ones that may be needed for rollback. Use docker image prune -a --filter "until=24h" to keep recent images.
docker-troubleshooting-guide THECODEFORGE.IO Container Crash Root Cause Layers From kernel to application: where failures originate Application Layer Health Check Failure | Zombie Process Accumulation Container Runtime PID 1 Reaping | Signal Propagation cgroups v2 Memory Limit Exceeded | Disk I/O Throttle | CPU Quota Kernel OOM Killer | Disk Full | Network Timeout Infrastructure DNS Resolution | Storage Exhaustion THECODEFORGE.IO
thecodeforge.io
Docker Troubleshooting Guide

Zombie Processes and PID 1 Problems

Containers run with PID 1 — the init process. If your app doesn't handle signal forwarding and reaping zombie children, you get a mess. Zombie processes accumulate, PID 1 doesn't respond to SIGTERM, and your container becomes unkillable.

This is a classic rookie mistake: running a Java app directly as java -jar app.jar. Java doesn't reap zombies. If your app spawns child processes (e.g., forking a shell command), those children become zombies when they exit, because PID 1 (Java) doesn't call wait().

The fix: use a lightweight init system like tini or dumb-init. Add it to your Dockerfile: ENTRYPOINT ["/usr/bin/tini", "--"] then CMD ["java", "-jar", "app.jar"]. Tini handles signal forwarding and reaping.

To check for zombies inside a container: docker exec ps aux | grep 'Z'. If you see zombie processes, you need an init system.

Dockerfile.with.tiniDOCKERFILE
1
2
3
4
5
6
7
# io.thecodeforge — DevOps tutorial
FROM openjdk:11-jre-slim
# Install tini
RUN apt-get update && apt-get install -y tini && rm -rf /var/lib/apt/lists/*
# Use tini as init
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["java", "-jar", "app.jar"]
🔥The Classic Bug:
If your container ignores docker stop and you have to docker kill it, you probably don't have an init system. The app isn't receiving SIGTERM. Add tini and test: docker stop should exit within 10 seconds.

Network Timeouts and DNS Resolution

Your container can't reach the database. But it worked yesterday. Network issues in Docker are often DNS-related. By default, containers use the host's DNS via /etc/resolv.conf. If that file is stale or the DNS server is unreachable, name resolution fails.

First, test DNS inside the container: docker exec <container> nslookup db.example.com. If it fails, check the container's /etc/resolv.conf: docker exec <container> cat /etc/resolv.conf. It should list valid nameservers.

Common fix: set --dns flag to a reliable DNS server like 8.8.8.8. Or use --dns-search to add search domains. For production, run a local DNS cache like dnsmasq in a sidecar container.

Another gotcha: Docker's default bridge network doesn't allow DNS resolution by container name. Use a user-defined network: docker network create mynet then docker run --network mynet. This enables DNS resolution using container names as hostnames.

dns_debug.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
# io.thecodeforge — DevOps tutorial
# Test DNS inside container
docker exec my_app nslookup db.example.com
# Output:
# Server:         8.8.8.8
# Address:        8.8.8.8#53
# Non-authoritative answer:
# Name:   db.example.com
# Address: 10.0.0.1
# Check resolv.conf
docker exec my_app cat /etc/resolv.conf
# Output:
# nameserver 8.8.8.8
# nameserver 8.8.4.4
Output
Server: 8.8.8.8
Address: 8.8.8.8#53
Non-authoritative answer:
Name: db.example.com
Address: 10.0.0.1
nameserver 8.8.8.8
nameserver 8.8.4.4
⚠ Production Trap:
Don't hardcode IP addresses in your app. Use container names or service discovery. If you must use IPs, they change on restart. Always use DNS or environment variables.

Health Checks: The First Line of Defense

You shouldn't wait for a crash to know something's wrong. Health checks let Docker monitor your container and restart it automatically if it becomes unhealthy. But most people write health checks that are too simple — they check if the process is running, not if the app is actually working.

A good health check tests a real endpoint: curl -f http://localhost:8080/health. It should return HTTP 200 within a timeout. Set --health-interval=30s, --health-retries=3, and --health-start-period=30s to give the app time to start.

In your Dockerfile: HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 CMD curl -f http://localhost:8080/health || exit 1.

To see health status: docker inspect --format '{{.State.Health.Status}}' . It returns healthy, unhealthy, or starting. If it's unhealthy, check the last 5 health check logs: docker inspect --format '{{json .State.Health.Log}}' .

Dockerfile.with.healthcheckDOCKERFILE
1
2
3
4
5
6
7
8
# io.thecodeforge — DevOps tutorial
FROM nginx:alpine
# Install curl for health check
RUN apk add --no-cache curl
# Add health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
  CMD curl -f http://localhost/ || exit 1
EXPOSE 80
💡Senior Shortcut:
Use --health-start-period to avoid premature restarts. Many apps need 10-30 seconds to initialize. Without this, Docker may restart your container in a loop during startup.

cgroups v2 Specific Error Catalog: 8+ Errors and Exact Fix Paths

Modern Linux distributions (Ubuntu 22.04+, Fedora 35+, RHEL 9+) use cgroups v2 by default. Docker's cgroups v2 support has matured, but several errors still bite engineers. Catalog:

  1. cgroup2: unknown option — Docker < 20.10 on cgroups v2. Fix: upgrade Docker to 20.10+.
  2. permission denied writing to /sys/fs/cgroup — container lacks CAP_SYS_ADMIN or cgroupns mode restricts writes. Fix: docker run --privileged or --security-opt systempaths=unconfined.
  3. write /sys/fs/cgroup/...: device or resource busy — cgroup fs already mounted with different options. Fix: ensure systemd.unified_cgroup_hierarchy=1 on kernel cmdline.
  4. containerd: failed to mount cgroup2 — containerd < 1.5 incompatible with kernel cgroup2. Fix: apt-get install --only-upgrade containerd.io.
  5. runc: symlink /sys/fs/cgroup — runc < 1.0.0-rc93. Fix: upgrade containerd which bundles runc.
  6. OCI runtime create failed: unable to get device controller — missing memory or io controller. Fix: enable CONFIG_CGROUP_MEMORY and CONFIG_BLK_CGROUP in kernel.
  7. failed to write "max" to memory.max — swap limit requires memory.swap.max which may be absent. Fix: set --memory-swap equal to --memory or configure kernel with CONFIG_MEMCG_SWAP=y.
  8. cgroup: fork failed: resource temporarily unavailable — cgroup pids limit hit. Fix: increase --pids-limit (default: unlimited in Docker, but systemd may set 4096).

Always check docker info | grep cgroup to see which cgroup version Docker is using. For mixed cgroups v1/v2 hosts, set "cgroupns": "host" in daemon.json.

check_cgroup_version.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash
# io.thecodeforge — DevOps tutorial

# Check which cgroup version Docker is using
docker info | grep -i cgroup
# Output: Cgroup Driver: systemd
#         Cgroup Version: 2

# Check kernel cgroup version
stat -fc %T /sys/fs/cgroup/
# Output: cgroup2fs (v2) or tmpfs (v1)

# List available cgroup controllers
cat /sys/fs/cgroup/cgroup.controllers
# Output: cpu io memory pids

# Fix common error: set daemon.json for cgroupv2 compatibility
echo '{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "cgroupns": "host"
}' > /etc/docker/daemon.json
Output
Cgroup Driver: systemd
Cgroup Version: 2
cgroup2fs
cpu io memory pids
⚠ Production Trap: Mixed cgroup Versions
If your host runs cgroups v2 but Docker is configured for v1, containers may start but OOM detection and resource accounting silently break. Always match Docker's cgroup driver to the host. Run docker info | grep Cgroup after any kernel upgrade.

Healthcheck Failure Debugging: Why Your Service Is Stuck in 'starting'

Docker health checks can fail in subtle ways. The most common: a service stuck in starting status indefinitely. This happens when --health-start-period is too short — the health check command runs before the app is ready, fails, and Docker keeps retrying until start_period elapses. After that, if the first check still fails, the container goes unhealthy. Symptoms: docker inspect --format '{{.State.Health.Status}}' shows starting for longer than expected. Debug with docker inspect --format '{{json .State.Health.Log}}' to see the last 5 health check outputs. If the health check command itself is failing (e.g., curl not found), install it in the Dockerfile or use a shell-based check like CMD pgrep nginx || exit 1. Cascading healthcheck failures: when service A depends on service B, and B's health check fails, A's health check may also fail (e.g., A checks B's endpoint). Break circular dependencies by making health checks independent. Use start_period generously (60s for Spring Boot, 30s for Node.js) and set retries to 3 with interval of 15-30s.

healthcheck_debug.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/bin/bash
# io.thecodeforge — DevOps tutorial

# Check health status
docker inspect --format '{{.State.Health.Status}}' my_container
# Output: starting (if stuck, check logs below)

# View health check log history
docker inspect --format '{{json .State.Health.Log}}' my_container | jq .
# Output:
# [
#   {
#     "Start": "2026-07-11T10:00:00Z",
#     "End": "2026-07-11T10:00:01Z",
#     "ExitCode": 1,
#     "Output": "curl: (7) Failed to connect to localhost port 8080"
#   }
# ]

# Fix: increase start period and verify command exists
docker run --health-cmd="curl -f http://localhost:8080/health || exit 1" \
  --health-interval=15s \
  --health-retries=5 \
  --health-start-period=60s \
  --health-timeout=5s \
  my_app
Output
starting
[
{
"Start": "2026-07-11T10:00:00Z",
"End": "2026-07-11T10:00:01Z",
"ExitCode": 1,
"Output": "curl: (7) Failed to connect to localhost port 8080"
}
]
💡Senior Shortcut: Test Health Check Commands Manually
Before adding a health check to Dockerfile, test the command manually: docker exec <container> curl -f http://localhost:8080/health. If it fails, tweak the command, not the start_period.

Exit Code Reference Table: What Each Code Means and How to Respond

Docker exit codes are your first diagnostic signal. Here is the comprehensive reference:

Exit CodeSignal / MeaningCommon CauseImmediate Action
0Clean exitApp intentionally stopped (e.g., batch job completed, cron finished)Expected — no action needed
1General errorApp-specific failure (uncaught exception, config error)Check docker logs for error message
125Daemon errorDocker daemon failed to start the container (invalid config, missing entrypoint)Check docker inspect for error message in State.Error
126Command not executableEntrypoint/CMD binary lacks +x permission or is a script without shebangchmod +x the binary or fix shebang line
127Command not foundEntrypoint/CMD binary does not exist in the imageVerify PATH and binary location inside container
130SIGINT (Ctrl+C)Container received Ctrl+C (interactive stop)Expected when manually stopping — no action
137SIGKILL (OOM)Container exceeded memory limit and was killed by OOM killerCheck `dmesggrep -i oom`, increase memory limit or fix leak
139SIGSEGV (segfault)Memory access violation, null pointer dereferenceCheck app logs, try --security-opt seccomp=unconfined to rule out seccomp
143SIGTERM (graceful stop)docker stop was issued, app received SIGTERM and exitedExpected during graceful shutdown — no action
255Exit status out of rangeDocker or shell returned an invalid exit code (rare)Check container logs and daemon logs for anomalies

Pro tip: Use docker inspect --format 'ExitCode={{.State.ExitCode}} OOM={{.State.OOMKilled}}' to quickly see both exit code and whether OOM was the cause.

exit_code_check.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
# io.thecodeforge — DevOps tutorial

# Quick exit code + OOM status
docker inspect my_container --format 'ExitCode={{.State.ExitCode}}  OOM={{.State.OOMKilled}}'
# Output: ExitCode=137  OOM=true

# Check all exit codes for recent restarts
docker inspect my_container --format '{{range .State.Health.Log}}{{.ExitCode}}{{"\n"}}{{end}}'

# List all stopped containers with exit codes
docker ps -a --format 'table {{.Names}}\t{{.Status}}'
Output
ExitCode=137 OOM=true
🔥Interview Gold: Exit Codes Are Universal
Exit codes 1-255 are POSIX standard, not Docker-specific. This means your debugging knowledge transfers to Kubernetes pod ExitCodes, systemd service exits, and any Linux process. Docker just adds codes 125-127 for daemon-specific failures.
OOM vs Disk Full Crash Patterns Comparing two common silent killers in production OOM (Memory) Disk Full Exit Code 137 (SIGKILL) 1 or 130 (SIGTERM) Logs No logs, container killed instantly Write errors before crash Detection dmesg shows OOM killer df -h shows 100% usage Recovery Increase memory limit or fix leak Clean up logs or expand volume cgroups v2 Error memory.max exceeded io.max write limit hit THECODEFORGE.IO
thecodeforge.io
Docker Troubleshooting Guide

PID 1 Signal Handling: Why Your Container Ignores SIGTERM and Requests Get Dropped

When you run docker stop, Docker sends SIGTERM to PID 1 inside the container. If PID 1 doesn't handle SIGTERM, the default behavior is to ignore it. After 10 seconds, Docker sends SIGKILL. This causes delayed deploys (orchestrator waits for the stop timeout), dropped in-flight requests (SIGKILL doesn't allow cleanup), and zombie processes (no reaper). The root cause: running the application directly as PID 1 (e.g., CMD ["java", "-jar", "app.jar"]). Java, Node.js, Python, and Go default binaries don't reap zombies or forward signals. The solution: use a lightweight init system like tini or dumb-init. In Dockerfile: ENTRYPOINT ["/usr/bin/tini", "--"] and CMD ["your-app"]. Tini ensures SIGTERM is forwarded to the child process and waits for it to exit before allowing the container to die. It also reaps zombie processes. For Node.js apps specifically, the --init flag on docker run achieves the same: docker run --init node:18-alpine node app.js. This inserts tini automatically. Verify: docker exec ps aux — PID 1 should be tini, your app should be PID 2 or higher.

Dockerfile.with.tiniDOCKERFILE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# io.thecodeforge — DevOps tutorial

# Option 1: Dockerfile with tini
FROM node:18-alpine
RUN apk add --no-cache tini
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "server.js"]

# Option 2: Simpler — use docker run --init
# docker run --init -d --name myapp node:18-alpine node server.js

# Verify PID 1
# $ docker exec myapp ps aux
# PID   USER     TIME  COMMAND
#   1   root     0:00 /sbin/tini -- node server.js
#   7   root     0:00 node server.js
#  14   root     0:00 ps aux
Output
PID USER TIME COMMAND
1 root 0:00 /sbin/tini -- node server.js
7 root 0:00 node server.js
⚠ The 10-Second Kill: Cost of Ignoring SIGTERM
If your orchestrator (Kubernetes, Nomad) waits for docker stop timeout, each delayed container adds 10 seconds to rolling updates. For a 50-service deployment, that's 8+ minutes of extra downtime. Init systems aren't optional — they're a deployment velocity multiplier.
● Production incidentPOST-MORTEMseverity: high

The 4GB Container That Kept Dying

Symptom
A Java microservice container exited with code 137 every 2 hours during peak traffic. Restarting fixed it temporarily.
Assumption
Team assumed a memory leak in the Java heap. They increased heap size and memory limit, but it still crashed.
Root cause
The container had a memory limit of 4GB, but the JVM's non-heap memory (Metaspace, thread stacks, direct buffers) plus the OS page cache consumed the rest. The OOM killer killed the container when total memory exceeded the limit.
Fix
Set -XX:MaxMetaspaceSize=256m and -XX:ReservedCodeCacheSize=128m in JVM flags. Also set --memory-reservation to 3GB to give the kernel better hints. No more OOM kills.
Key lesson
  • Container memory limits apply to the whole cgroup, not just the app heap.
  • Always account for non-heap memory, and use docker stats to see real usage before setting limits.
Production debug guideSystematic recovery paths for the failure modes engineers actually hit.3 entries
Symptom · 01
Container exits with code 137 (OOM)
Fix
1. Run dmesg | grep -i oom | tail -10 to confirm OOM kill. 2. Run docker stats <container> to see memory usage before crash. 3. Increase memory limit or fix memory leak. 4. Add --memory-reservation to 70% of limit.
Symptom · 02
Container exits with code 139 (segfault)
Fix
1. Check app logs for core dump. 2. Run docker run --security-opt seccomp=unconfined <image> to test if seccomp is blocking syscalls. 3. If it works, create a custom seccomp profile. 4. Otherwise, debug the app binary.
Symptom · 03
Container runs but is unresponsive
Fix
1. Check health check endpoint: docker exec <container> curl -f http://localhost:8080/health. 2. Check logs: docker logs --tail 50 <container>. 3. Check resource usage: docker stats <container>. 4. If CPU 100%, check for infinite loop. If memory 100%, OOM imminent.
★ Docker Troubleshooting Guide Triage Cheat SheetFirst-response commands for when things go wrong — copy-paste ready.
Container exits with code `137`
Immediate action
Check OOM killer logs
Commands
dmesg | grep -i oom | tail -10
docker inspect <container> --format '{{.State.ExitCode}}'
Fix now
Increase memory limit or fix memory leak. Add --memory-reservation.
Container exits with code `139`+
Immediate action
Check for segfault
Commands
docker logs <container> | tail -20
docker run --security-opt seccomp=unconfined <image>
Fix now
If seccomp is the issue, create a custom profile. Otherwise debug the app.
Container exits with code `0` immediately+
Immediate action
Check entrypoint/CMD
Commands
docker inspect <container> --format '{{.Config.Cmd}}'
docker run -it <image> sh
Fix now
Fix entrypoint script or CMD syntax.
Container runs but is unresponsive+
Immediate action
Check health endpoint
Commands
docker exec <container> curl -f http://localhost:8080/health
docker stats <container>
Fix now
If CPU 100%, kill and restart. If memory high, increase limit. If deadlock, restart.
⚙ Quick Reference
10 commands from this guide
FileCommand / CodePurpose
check_exit_code.shdocker inspect my_app --format '{{.State.ExitCode}}'Why Containers Crash
memory_debug.shdocker stats my_app --no-streamMemory Troubleshooting
disk_debug.shdf -h /var/lib/dockerDisk Full
Dockerfile.with.tiniFROM openjdk:11-jre-slimZombie Processes and PID 1 Problems
dns_debug.shdocker exec my_app nslookup db.example.comNetwork Timeouts and DNS Resolution
Dockerfile.with.healthcheckFROM nginx:alpineHealth Checks
check_cgroup_version.shdocker info | grep -i cgroupcgroups v2 Specific Error Catalog
healthcheck_debug.shdocker inspect --format '{{.State.Health.Status}}' my_containerHealthcheck Failure Debugging
exit_code_check.shdocker inspect my_container --format 'ExitCode={{.State.ExitCode}} OOM={{.State...Exit Code Reference Table
Dockerfile.with.tiniFROM node:18-alpinePID 1 Signal Handling

Key takeaways

1
Exit codes are your first clue
137 = OOM, 139 = segfault, 0 = clean exit (maybe unexpected).
2
Memory limits apply to the whole cgroup, not just the app heap. Always account for non-heap memory.
3
Always use an init system (tini/dumb-init) to handle signals and reap zombie processes.
4
Health checks should test real app functionality, not just process existence.
5
Exit codes are your first diagnostic signal
memorize 137 (OOM), 139 (segfault), 143 (SIGTERM), 125 (daemon error), and you can diagnose 90% of production crashes in 10 seconds.
6
A proper init system (tini/dumb-init) is not optional for any container that spawns child processes or needs graceful shutdown
it's the difference between 1-second deploys and 10-second dead container waits.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How does Docker's OOM killer decide which container to kill when multipl...
Q02SENIOR
When would you choose `docker restart` vs. fixing the root cause in a pr...
Q03SENIOR
What happens when you run `docker stop` and the container doesn't respon...
Q04JUNIOR
What is the difference between `docker logs` and `docker inspect` for tr...
Q05SENIOR
A container in production is crashing every hour with exit code 137. You...
Q06SENIOR
How would you design a Docker host to handle 100 containers with varying...
Q07SENIOR
Walk through your systematic troubleshooting methodology when a producti...
Q08SENIOR
You're debugging a container on a cgroups v2 host that exits with 137 im...
Q01 of 08SENIOR

How does Docker's OOM killer decide which container to kill when multiple containers exceed their memory limits?

ANSWER
Docker uses the kernel's OOM killer, which scores processes based on oom_score_adj. By default, Docker sets oom_score_adj to -500 for containers, making them less likely to be killed than host processes. But if a container exceeds its limit, the kernel kills the process inside that container. The score is based on memory usage, runtime, and oom_score_adj. You can adjust with --oom-score-adj.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Why does my container keep restarting with exit code 137?
02
What's the difference between `docker stop` and `docker kill`?
03
How do I check if a container is using too much memory?
04
My container exits immediately with code 0. What's wrong?
05
What does exit code 139 mean in Docker and how do I fix it?
06
How do I fix 'permission denied writing to /sys/fs/cgroup' on a cgroups v2 host?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.

Follow
Verified
production tested
July 18, 2026
last updated
2,466
articles · all by Naren
🔥

That's Docker. Mark it forged?

7 min read · try the examples if you haven't

Previous
Docker Resource Limits and cgroups
29 / 43 · Docker
Next
Docker with GitHub Actions