Containers Intermediate

Troubleshooting Docker Container Exit Code 137 (OOM Killed) on macOS Local Environment

Resolve Docker containers exiting with code 137 (OOM killed) on macOS. Learn to diagnose and fix memory limits for stable local development.

👨‍💻
Senior Systems Architect • Verified in Staging Labs

Resolve Docker containers exiting with code 137 (OOM killed) on macOS. Learn to diagnose and fix memory limits for stable local development.

When working with Docker containers on your local macOS development environment, encountering an Exited (137) status can be a frustrating roadblock. This specific exit code is a clear indicator that your container was forcibly terminated by the operating system's Out-Of-Memory (OOM) killer. Essentially, your application within the container tried to consume more memory than was available, leading to its abrupt shutdown. This guide will walk you through understanding why this happens on macOS and provide a robust set of solutions to get your containers running stably.

Symptom & Error Signature

You will typically observe this issue when running docker ps or docker ps -a, where your container shows an Exited (137) status. Attempting to start the container again might result in the same immediate exit.

Here's what you might see in your terminal:

$ docker ps -a
CONTAINER ID   IMAGE                 COMMAND                  CREATED         STATUS                         PORTS     NAMES
a1b2c3d4e5f6   my-memory-hog-app     "node server.js"         2 minutes ago   Exited (137) 1 minute ago                my-app-service

When you inspect the logs of the affected container, you usually won't find specific application-level memory errors directly explaining the OOM kill, as the process was terminated externally by the kernel. However, if the application has its own memory monitoring, you might see warnings preceding the crash.

$ docker logs a1b2c3d4e5f6
[... application startup messages ...]
[... application processing tasks ...]
# No explicit OOM message from the app itself, as it was killed abruptly
# The last few lines might indicate the task it was performing when killed
# e.g., "Starting heavy data processing..."

Root Cause Analysis

On macOS, Docker Desktop runs a lightweight Linux virtual machine (VM) that hosts all your containers. This is a critical architectural detail often overlooked, and it's the primary reason for OOM kills with exit code 137.

  1. Docker Desktop's Allocated Resources: By default, Docker Desktop allocates a specific amount of CPU, memory, and swap space to this internal Linux VM. If your applications running within containers collectively attempt to use more memory than what has been allocated to the Docker Desktop VM, the VM's kernel will invoke its Out-Of-Memory (OOM) killer.

  2. Linux OOM Killer: The Linux kernel's OOM killer is a mechanism designed to maintain system stability when memory resources are exhausted. When triggered, it selects and terminates processes (often the ones consuming the most memory) to free up RAM. An exit code 137 specifically means the process received a SIGKILL signal (signal 9), which is typically sent by the OOM killer or explicitly by an administrator (kill -9).

  3. Container-Specific Limits vs. VM Limits: Even if you've set memory limits for individual containers (e.g., via docker run --memory or docker-compose's mem_limit), these limits operate within the overall memory allocated to the Docker Desktop VM. If your container requests more memory than its own limit, it can be OOM killed. More commonly, if the sum of all active containers' memory usage (or a single very large container) exceeds the VM's total allocated memory, the VM's kernel will OOM kill one or more processes to prevent the entire VM from crashing.

  4. Memory-Intensive Workloads: Common culprits include:

    • Large build processes (e.g., compiling complex projects, npm install with many dependencies).
    • Java applications with default or inadequately tuned JVM heap sizes.
    • Node.js applications processing large datasets.
    • Database containers (PostgreSQL, MySQL, MongoDB) performing large imports or complex queries.
    • Data science workloads with large in-memory objects.

Step-by-Step Resolution

To resolve exit code 137 errors, you typically need to address memory availability at both the Docker Desktop VM level and, if necessary, at the individual container/application level.

1. Inspect Container Logs and Resource Usage

Before making changes, gather information about your container's behavior.

  • Review Application Logs:

    docker logs <container_name_or_id>
    

    Look for any application-specific warnings or errors related to memory, even if the OOM kill itself isn't directly logged.

  • Monitor Real-time Usage (if container stays up long enough):

    docker stats <container_name_or_id>
    

    This command provides real-time CPU, memory, network, and disk I/O usage. If your container runs for a short period before crashing, you might catch a rapid spike in memory usage.

  • Check Configured Memory Limits for the Container:

    docker inspect --format='{{.HostConfig.Memory}}' <container_name_or_id>
    docker inspect --format='{{.HostConfig.MemorySwap}}' <container_name_or_id>
    

    These commands show any explicit memory (--memory) or swap (--memory-swap) limits configured for the specific container. A value of 0 means no limit is set for that container.

docker stats shows current usage, which might not capture the precise peak usage moment before an OOM kill. If the container dies quickly, these stats might not be very helpful for historical analysis.

2. Increase Docker Desktop VM Memory Allocation

This is the most common and often the most effective solution for local macOS environments. You need to provide more RAM to the underlying Linux VM that Docker Desktop uses.

  1. Open Docker Desktop Settings: Click the Docker icon in your macOS menu bar -> Settings (or Preferences).

  2. Navigate to Resources -> Advanced: Here you'll find sliders for CPU, Memory, and Swap.

  3. Increase Memory: Adjust the "Memory" slider to a higher value. A good starting point might be 4GB, 6GB, or even 8GB, depending on your machine's total RAM and the demands of your containers.

    Do not allocate too much memory to Docker Desktop, as this can starve your macOS host, leading to overall system slowdowns and instability. Aim for a balance, leaving enough memory for your macOS applications. A common recommendation is to allocate no more than 50-75% of your total physical RAM to Docker.

  4. Apply & Restart: Click "Apply & Restart" to save the changes and restart the Docker Desktop VM. Your containers will be stopped and restarted automatically after the VM reboots.

3. Set or Increase Container-Specific Memory Limits

If you have a particularly memory-hungry application, or if you want to prevent a single container from consuming all available VM memory, set explicit memory limits for that container.

  • Using docker run:

    docker run -it --memory="2g" --memory-swap="4g" my-app-image node server.js
    
    • --memory="2g": Sets a hard memory limit of 2 Gigabytes. If the container tries to exceed this, it will be OOM killed.
    • --memory-swap="4g": Sets the combined memory and swap limit to 4 Gigabytes. This means the container can use 2GB of RAM and an additional 2GB of swap space (4GB total minus 2GB RAM).
  • Using docker-compose.yml:

    version: '3.8'
    services:
      my-app:
        image: my-app-image:latest
        command: node server.js
        deploy:
          resources:
            limits:
              memory: 2g # Hard memory limit
            reservations:
              memory: 1g # Optional: ensure at least 1GB is reserved
        # Alternatively, for simpler cases, can use:
        # mem_limit: 2g
        # mem_swap_limit: 4g
    

    The deploy.resources.limits syntax is preferred for production and advanced scenarios. mem_limit and mem_swap_limit are older but still widely used alternatives for development. mem_swap_limit works in conjunction with mem_limit; if mem_swap_limit is greater than mem_limit, the difference is the amount of swap the container can use. If mem_swap_limit is equal to mem_limit, swap is effectively disabled for that container.

4. Optimize Application Resource Consumption

Sometimes, simply increasing limits isn't enough, or it's a workaround for an inefficient application. Optimizing your application is crucial for long-term stability and performance.

  • Review Application Code:

    • Look for memory leaks, inefficient data structures, or excessive caching.
    • Profile your application to identify memory hotspots.
  • Tune Runtime Parameters:

    • Java (JVM): Explicitly set JVM heap sizes. Default settings often assume larger host environments.
      ENV JAVA_OPTS="-Xmx1g -Xms550m -XX:MaxMetaspaceSize=256m"
      CMD java $JAVA_OPTS -jar myapp.jar
      
      This sets max heap to 1GB and initial heap to 550MB. Adjust as needed.
    • Node.js: Increase the V8 old space size for memory-intensive Node.js applications.
      ENV NODE_OPTIONS="--max-old-space-size=2048" # Allows Node.js to use up to 2GB
      CMD node server.js
      
    • PHP-FPM: Adjust pm.max_children, pm.start_servers, pm.min_spare_servers, pm.max_spare_servers, and crucially memory_limit in php.ini or the FPM pool configuration.
    • Databases (e.g., PostgreSQL): Tune parameters like shared_buffers, work_mem, maintenance_work_mem, effective_cache_size in postgresql.conf within the container.
  • Use Smaller Base Images:

    • Alpine-based images (alpine, node:alpine, python:alpine) are significantly smaller than their Debian counterparts, leading to a reduced overall footprint and sometimes better resource efficiency.
    FROM node:18-alpine
    # ... rest of your Dockerfile
    
  • Multi-stage Builds:

    • Use multi-stage builds to ensure your final image only contains the necessary runtime artifacts, excluding build tools and temporary files. This reduces image size and can indirectly improve startup times and memory usage.

5. Consider Disabling Swap for Specific Containers (Advanced)

While swap can prevent an immediate OOM kill, excessive swapping can severely degrade performance. For critical applications where predictable memory usage is key, you might want to effectively disable swap for that container.

  • Using docker run:

    docker run -it --memory="2g" --memory-swap="2g" my-app-image node server.js
    

    When --memory-swap is set equal to --memory, the container has no dedicated swap space beyond its RAM limit.

  • Using docker-compose.yml:

    version: '3.8'
    services:
      my-app:
        image: my-app-image:latest
        mem_limit: 2g
        mem_swap_limit: 2g # Effectively disables swap for this container
    

Disabling swap means that if your application tries to exceed its mem_limit even slightly, it will be immediately OOM killed. Use this only if you are confident in your application's memory profile and prefer a hard crash over performance degradation from swapping.

6. Monitor and Iterate

After implementing any of these changes, it's crucial to monitor your application's behavior and resource usage.

  1. Rerun Your Workload: Execute the operations that previously caused the OOM error.
  2. Use docker stats: Observe memory usage while the container is running.
  3. Check docker logs: Ensure no new application errors arise.
  4. Gradually Adjust: If the problem persists, incrementally increase Docker Desktop's memory, or refine container-specific limits and application optimizations until stability is achieved.

By systematically addressing memory allocation at both the Docker Desktop VM level and the container/application level, you can effectively resolve exit code 137 issues and ensure a stable local development environment on macOS.

👨‍💻

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.