Marcio Cunha

Why Docker crashes with OOM Killer and how to check limits

Learn why your Docker containers suffer from sudden OOM Killer events and discover how to investigate memory usage, RSS limits, and heap overflows on Linux.

Marcio Cunha•3 min
Also available in:PortuguêsEspañol
Summary
  • The Linux kernel terminates processes abruptly when memory consumption exceeds configured thresholds.
  • Resident Set Size represents the actual physical memory currently utilized by the container process.
  • Omitting explicit resource limits in Docker Compose exposes the underlying host to system-wide crashes.
  • Native system tools like dmesg help confirm the exit code 137 generated by the operating system kernel.
  • Tuning garbage collection parameters in managed runtimes prevents heap overflows and unexpected drops.

The mystery of the container that vanishes without a trace

Anyone working with lightweight virtualization and distributed systems has experienced that frustrating moment when a service simply stops responding. You look at the screen, run a status command, and realize the container is gone, leaving behind only a cryptic exit code. In practice, this means the operating system had to make a drastic decision to save the integrity of the entire machine. When physical RAM runs out or hits a ceiling, the OOM Killer steps in—a Linux kernel protection mechanism that identifies and terminates greedy processes.

To understand why this happens, it is worth looking at the concept of Resident Set Size, or RSS. Simply put, RSS is the actual amount of physical memory a program is occupying at any given moment, excluding temporary disk swaps. If you want to dive deeper into infrastructure diagnostics, be sure to check out our guide on how to audit CPU and memory limits on hanging containers, which complements this stability analysis.

How the Linux kernel calculates and enforces memory limits

Docker relies on native Linux kernel features known as cgroups, or control groups. Think of cgroups as virtual fences that dictate precisely how much power, processing power, and memory a specific group of processes can consume. When you start a container without setting restrictions, it inherits the host machine's total capacity, which can easily exhaust server resources and take down other essential applications.

When we configure a strict memory limit using the memory directive in Docker Compose, the kernel builds a rigid fence. If container consumption crosses that boundary—combining runtime usage and system buffers—the protection mechanism does not hesitate. It triggers the internal OOM Killer of that cgroup, immediately stopping the main process with the dreaded exit code 137. To diagnose this behavior accurately, consult our tutorial on how to audit the OOM Killer with dmesg, which is essential for quick investigations.

Investigating heap overflows and hidden memory consumption

Often, the blame for an abrupt termination does not lie with Docker itself, but with how the programming language manages its internal workspaces. In environments like Node.js, Python, or Java, programs allocate a large block of internal memory called the heap to store objects and dynamic variables. If the application creates excess data faster than the garbage collector can discard it, the heap swells uncontrollably until it breaches the container's hard limit.

In practice, this creates a visual trap: monitoring dashboards show low overall memory consumption, but the Linux kernel detects an instantaneous RSS spike that crosses the cutoff line. To diagnose specific scenarios in JavaScript applications, read the tips on how to debug Node.js memory leaks using heap snapshots, making it easier to pinpoint which objects are cluttering memory.

Step-by-step guide to monitor and configure safe limits

Configuring defensive policies prevents software flaws from crashing your entire production environment. To ensure your services operate within safe and predictable margins, follow this verification and practical adjustment routine:

  1. Inspect the system termination history by running the command to verify if there was kernel intervention.
    dmesg -T | grep -i oom
  2. Check the specific container exit logs to confirm if the returned exit code was precisely 137.
    docker inspect --format='{{.State.ExitCode}}' container_name
  3. Define clear memory and swap limits inside your orchestration file to contain leaks before they impact the server.
    services:  web:    image: my-app    deploy:      resources:        limits:          memory: 512M          reservations:            memory: 256M

Conclusion and best practices to prevent production surprises

Managing container memory requires constant vigilance and the correct use of observability tools. Setting clear limits in Docker, monitoring RSS behavior, and understanding how your favorite runtime's garbage collector operates are fundamental steps to maintaining stability. With these precautions, you turn silent, intermittent failures into predictable and easy-to-resolve incidents.

Ultimately, a resilient system is not one that never fails, but one that isolates issues before they impact the user experience. Adjusting infrastructure parameters and periodically reviewing heap consumption ensures your architecture continues to grow in a sustainable and secure manner.