Containers Advanced

Troubleshooting Docker Container Exit Code 137 (OOM Killed) in Windows WSL2 Ubuntu

Fix Docker container exit code 137 (OOM killed) in WSL2 Ubuntu. Diagnose and resolve out-of-memory errors by configuring WSL2 and Docker memory limits effectively.

๐Ÿ‘จโ€๐Ÿ’ป
Senior Systems Architect • Verified in Staging Labs

Fix Docker container exit code 137 (OOM killed) in WSL2 Ubuntu. Diagnose and resolve out-of-memory errors by configuring WSL2 and Docker memory limits effectively.

A Docker container exiting unexpectedly with code 137 on a Windows host using WSL2 with an Ubuntu distribution is a common and often frustrating issue. This error typically indicates that your container, or the underlying WSL2 environment, has run out of memory and has been forcibly terminated by the Linux Out-Of-Memory (OOM) killer. When this happens, your application service will become unavailable, leading to downtime and requiring manual intervention to restart. Understanding the interaction between Docker Desktop, WSL2, and the Linux kernel's memory management is key to resolving this persistent problem.

Symptom & Error Signature

Users typically observe their Docker containers stopping abruptly without any explicit command, often resulting in their web application or service becoming unreachable. Upon inspecting the container's status or logs, the exit code 137 will be prominently displayed.

Typical docker ps -a output:

CONTAINER ID   IMAGE                 COMMAND                  CREATED         STATUS                      PORTS     NAMES
a1b2c3d4e5f6   my-app-image:latest   "nginx -g 'daemon ofโ€ฆ"   2 minutes ago   Exited (137) 1 minute ago             my-web-app

And examining the container logs will usually show little to no specific application error, as the process was killed externally:

docker logs a1b2c3d4e5f6
# (Potentially no specific errors, or just the last few application logs before the kill signal)

The most conclusive evidence comes from the WSL2 distribution's kernel logs, accessible via dmesg within your Ubuntu WSL2 instance:

# Inside your Ubuntu WSL2 instance
dmesg | grep -i "oom-killer"

Expected dmesg output (or similar):

[ 1234.567890] oom-kill:constraint=CONTAINER,nodemask=(null),cpuset=/,mems_cgroup=user.slice/user-1000.slice/session-2.scope/docker/a1b2c3d4e5f6.scope,task_memcg=/user.slice/user-1000.slice/session-2.scope/docker/a1b2c3d4e5f6.scope,task=node,pid=12345,uid=0
[ 1234.567890] Memory cgroup out of memory: Killed process 12345 (node) total-vm:4500000kB, anon-rss:3800000kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:8765kB oom_score_adj:0
[ 1234.567890] oom_reaper: reaped process 12345 (node), now dead

Root Cause Analysis

Exit code 137 is a standard Unix exit code indicating that a process was terminated by a SIGKILL signal (signal 9). In the context of Docker and Linux, this almost universally points to the kernel's Out-Of-Memory (OOM) killer terminating the process because the system, or the container's allocated memory, was exhausted.

Here's a breakdown of the underlying reasons:

  1. WSL2 Global Memory Limit Exhaustion:

    • Docker Desktop runs its Linux daemon within a dedicated WSL2 virtual machine. By default, WSL2 dynamically allocates memory up to a certain percentage of your host's RAM (e.g., 50% on Windows 10, or 80% on Windows 11, capped at specific amounts).
    • If your Docker containers, combined with other processes running within the WSL2 instance (e.g., VS Code remote, other CLI tools), collectively exceed this default WSL2 memory limit, the Linux kernel inside the WSL2 VM will trigger its OOM killer.
    • The OOM killer selects a "guilty" process (often your memory-intensive Docker container) and terminates it to free up resources, resulting in exit code 137.
  2. Docker Container Specific Memory Limit Exhaustion:

    • Even if the overall WSL2 memory is sufficient, individual Docker containers can be configured with their own memory limits (e.g., using -m or mem_limit in Docker Compose).
    • If a container's application exceeds its specific allocated memory limit, the Docker daemon (which uses cgroups to enforce these limits) will again cause the Linux kernel to send a SIGKILL to the offending process. This is a common scenario for applications with memory leaks or those that simply require more resources than initially provisioned.
  3. Application Memory Leaks or Inefficiency:

    • The application running inside the container might have a memory leak or be inefficiently designed, gradually consuming more and more RAM until it hits a hard limit.
    • Common culprits include long-running data processing jobs, complex web applications, or services with large in-memory caches that are not properly managed.
  4. Host Resource Contention:

    • While less direct for exit code 137 within WSL2, heavy memory usage by other applications on your Windows host can indirectly starve the WSL2 VM of available physical RAM, exacerbating the OOM issue.

Step-by-Step Resolution

Addressing the exit code 137 OOM error requires a systematic approach, starting from the global WSL2 memory configuration down to individual container and application-level optimizations.

1. Verify the OOM Condition and Identify Culprit

Before making changes, confirm the OOM event and get insights into resource usage.

  • Check Docker Container Status:

    docker ps -a
    

    Look for containers with Exited (137).

  • Review WSL2 Kernel Logs for OOM Killer: Open your Ubuntu WSL2 terminal and execute:

    dmesg -T | grep -i "oom-killer" | tail -n 20
    

    This command prints recent kernel messages related to the OOM killer, often pinpointing the process (and thus likely the container) that was terminated.

  • Monitor Container Resource Usage: Use docker stats to get real-time memory usage for running containers.

    docker stats
    

    This helps identify which container is consuming the most memory just before a crash. You can also use docker inspect <container_id> to view configured memory limits.

2. Adjust WSL2 Global Memory Limits

This is often the most critical step, as WSL2's default memory allocation might be insufficient for demanding Docker workloads.

This configuration applies to all WSL2 distributions. If you have other memory-intensive WSL2 instances, they will also compete for this increased memory.

  1. Create or Edit .wslconfig: On your Windows host, navigate to your user profile directory (C:Users<YourUsername>). Create a file named .wslconfig if it doesn't exist, or edit it if it does.

    # In C:Users<YourUsername>.wslconfig
    [wsl2]
    memory=8GB  # Set to a value appropriate for your host system (e.g., 50% of host RAM)
    processors=4 # Optional: Specify the number of processors
    

    Explanation:

    • memory: This sets a hard cap on the amount of RAM that the WSL2 VM (and thus Docker Desktop's engine) can consume from your Windows host. Choose a value that leaves enough RAM for your host OS and other Windows applications. A good starting point is around 50-70% of your total physical RAM, but adjust based on your host's total memory (e.g., 8GB for a 16GB system, 12-16GB for a 32GB system).
    • processors: (Optional but recommended) Limits the number of virtual processors WSL2 can use.
  2. Shutdown WSL2 and Restart Docker Desktop: Open a Windows PowerShell or Command Prompt as Administrator and run:

    wsl --shutdown
    

    This command will stop all running WSL2 distributions. After it completes, restart Docker Desktop from your system tray. Docker Desktop will reinitialize its WSL2 backend with the new memory limits.

3. Configure Docker Desktop WSL 2 Backend Resources

While .wslconfig sets the global WSL2 VM limits, Docker Desktop also has its own resource settings that can indirectly affect performance and stability.

  1. Open Docker Desktop Settings: Click the Docker Desktop icon in your system tray, then navigate to Settings (the gear icon).

  2. Navigate to Resources: Go to Resources -> WSL INTEGRATION. Ensure your Ubuntu distribution is enabled. Go to Resources -> Advanced. Here, you might see options for "Memory" and "Swap" used by Docker.

    • If Use the recommended settings is checked, Docker Desktop typically tries to respect the .wslconfig limits.
    • If you have manually adjusted "Memory" or "Swap" here, ensure they are not set to excessively low values that could contradict your .wslconfig settings. In most cases, letting .wslconfig manage the primary memory allocation for the WSL2 VM is best.

4. Increase Specific Docker Container Memory Limits

If the global WSL2 memory is ample, but a particular container is still getting OOM killed, it's likely hitting its own Docker-enforced memory limit.

  • For docker run commands: Add the -m (or --memory) flag:

    docker run -d --name my-web-app -p 80:80 -m 2g --cpus 1 my-app-image:latest
    

    This example limits the container to 2GB of RAM.

  • For docker-compose.yml: Add the mem_limit and optionally cpus under the service definition:

    version: '3.8'
    services:
      web:
        image: my-app-image:latest
        container_name: my-web-app
        ports:
          - "80:80"
        deploy:
          resources:
            limits:
              memory: 2G
              cpus: '1.0' # 1 CPU core
    

    In Docker Compose v3, memory limits are specified under deploy.resources.limits.memory for Swarm compatibility, but Docker Desktop also honors them for single-host deployments.

    After modifying docker-compose.yml, run:

    docker-compose up -d --build --force-recreate
    

5. Optimize Application Memory Usage within the Container

While increasing limits helps, a truly robust solution often involves optimizing the application itself.

  • Identify Memory Hogs: Use profiling tools specific to your application's language/framework (e.g., JVM profilers for Java, go tool pprof for Go, memory_profiler for Python) to pinpoint memory leaks or inefficient data structures. Inside a running container, you can also use htop (after installing apt update && apt install -y htop) or smem to see process memory usage.

  • Tune Application Settings:

    • JVM Applications: Adjust JVM heap size settings (e.g., -Xmx, -Xms) to be appropriate for the container's allocated memory. Ensure they are less than the Docker container's mem_limit.
    • Node.js: Consider --max-old-space-size if V8 is consuming too much memory.
    • PHP-FPM: Adjust memory_limit in php.ini and pm.max_children settings in php-fpm.conf.
    • Databases: Tune buffer/cache sizes (e.g., innodb_buffer_pool_size for MySQL/MariaDB, shared_buffers for PostgreSQL).
  • Reduce Container Bloat: Use multi-stage builds in your Dockerfile to create smaller, more efficient final images, reducing the memory footprint of the container itself. Avoid installing unnecessary packages.

6. Monitor and Profile System Resources

Continuous monitoring is essential to detect recurring OOM issues or to catch potential memory growth before it leads to a crash.

  • Docker Stats & Events: Use docker stats for real-time overview. For historical data or event-driven alerts, consider using Docker's event stream: docker events --filter type=container --filter event=oom.

  • WSL2 Resource Monitoring: Inside your Ubuntu WSL2 instance, use standard Linux tools like htop, free -h, or top to monitor the overall memory usage of the WSL2 VM.

  • Prometheus/Grafana Integration: For more advanced setups, deploy monitoring agents like node_exporter (in WSL2) and cadvisor (as a Docker container) to collect metrics. Visualize these metrics using Prometheus and Grafana dashboards to track memory, CPU, and disk I/O over time, helping to identify trends and predict OOM events.

By systematically applying these steps, you can effectively diagnose and resolve exit code 137 related to Out-Of-Memory errors for your Docker containers running on Windows WSL2 Ubuntu. Remember that finding the right balance of memory allocation requires understanding your application's needs and your host system's capabilities.

๐Ÿ‘จโ€๐Ÿ’ป

Johnathon Wheeler

Senior Systems Architect & DevOps Engineer • Austin, TX

Connect on LinkedIn →

Johnathon has over 16 years of hands-on experience designing, debugging, and scaling Linux web hosting stacks, container clusters, and high-availability database architectures. Every guide on ButItWorkedLocal is independently tested against Debian 12, Ubuntu 24.04/22.04 LTS, Rocky Linux, and Docker environments to guarantee reproducibility in production.

๐Ÿ›ก๏ธ

Our Production Verification Guarantee

Encountering a bug not covered here or running a non-standard kernel configuration? Our solutions are continually refined against real production incidents. Submit an environment trace for our editorial team to replicate.