Resolve Docker containers exiting with code 137 due to OOM kills on Alpine Linux. Learn to identify, diagnose, and fix out-of-memory issues effectively.
When a Docker container abruptly stops functioning, exhibiting an "Exited (137)" status, it's a strong indicator that the Linux kernel's Out-Of-Memory (OOM) killer has terminated the process. This guide provides a comprehensive, expert-level approach to diagnose and resolve these critical memory-related issues, specifically in environments utilizing Alpine Linux base images for their Docker containers.
Symptom & Error Signature
The most prominent symptom is a container that repeatedly crashes, fails to start, or becomes unresponsive, leading to service disruption. Your application, whether it's a web server like Nginx, a database, or a custom microservice, will cease to function.
You'll typically observe the following:
- Container Status:
-
`bash - $ docker ps -a
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- a1b2c3d4e5f6 my-alpine-app:latest "node /app/index.js" 2 minutes ago Exited (137) 5 seconds ago web_app
-
`
- Docker Daemon Logs (on the host system):
- These logs often provide the first explicit mention of an OOM event.
-
`bash - $ sudo journalctl -u docker.service | grep -i "oom|killed process"
- Jul 15 10:30:45 hostname dockerd[1234]: containerd/task/v2/sender/pipe.go:39: "OOM killer killed containerida1b2c3d4e5f6"
- Jul 15 10:30:45 hostname dockerd[1234]: shimexit: containerid_a1b2c3d4e5f6 exit status 137
-
`
- Kernel Logs (
dmesgon the host system): - The definitive evidence comes from the kernel itself, detailing the OOM event.
-
`bash - $ sudo dmesg -T | grep -i "oom-killer|killed process"
- [Wed Jul 15 10:30:44 2026] node invoked oom-killer: gfpmask=0x100cca(GFPHIGHUSERMOVABLE|GFPCOMP), order=0, oomscoreadj=0
- [Wed Jul 15 10:30:44 2026] oom-kill:constraint=CONTAINER,nodemask=(null),cpuset=docker/a1b2c3d4e5f6,memsallowed=0,globaloom,task_memcg=/docker/a1b2c3d4e5f6,task=node,pid=5678,uid=0
- [Wed Jul 15 10:30:44 2026] Memory cgroup out of memory: Killed process 5678 (node) total-vm:450000kB, anon-rss:400000kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:800kB oomscoreadj:0
- [Wed Jul 15 10:30:44 2026] Tasks state (memory and swap accounts):
- [Wed Jul 15 10:30:44 2026] [ pid ] uid tgid totalvm rss nrptes nrpmds swapents oomscore_adj name
- [Wed Jul 15 10:30:44 2026] [ 5678] 0 5678 112500 100000 200 0 0 0 node
- [Wed Jul 10:30:44 2026] oom-killer: Kill process 5678 (node) score 1000 or sacrifice child
- [Wed Jul 10:30:44 2026] Killed process 5678 (node) total-vm:450000kB, anon-rss:400000kB, file-rss:0kB, shmem-rss:0kB
-
` - This output clearly shows
oom-killerbeing invoked and targetingprocess 5678 (node)within the specified Docker cgroup.
Root Cause Analysis
The "Exited with code 137" error specifically means the container process received a SIGKILL signal (signal 9). When this is combined with "OOM killed," it unambiguously indicates that the Linux kernel's Out-Of-Memory (OOM) killer decided to terminate the container's main process.
Here's a breakdown of the underlying reasons:
- Linux OOM Killer: This is a critical kernel mechanism designed to maintain system stability. When the system's memory (RAM + swap) is exhausted, the OOM killer steps in to free up resources by terminating one or more processes. It uses an internal heuristic (
oom_score) to decide which processes are "least important" to kill.
- Docker Memory Limits (cgroups): Docker leverages Linux Control Groups (cgroups) to isolate and limit resource usage for containers.
- *
mem_limit(or-mflag): This sets the maximum amount of RAM a container can use. - *
memswap_limit(or--memory-swapflag): This defines the combined limit for RAM and swap space. - If a container's processes attempt to allocate memory beyond its
memlimit, the OOM killer is triggered within that container's cgroup*, effectively killing the container without affecting other processes on the host or in other containers (unless the host itself is completely out of memory). Ifmemswaplimitis hit, and there's no more swap available, the OOM killer will also be triggered.
- Application Memory Profile:
- * Memory Leak: The application has a bug that causes it to continuously consume more memory over time without releasing it.
- * Memory Spike: The application might have a legitimate, but unexpected, memory spike during certain operations (e.g., loading large datasets, complex computations, garbage collection cycles), especially during startup or peak load.
- * Incorrect Configuration: Application-specific memory settings (e.g., JVM heap size
-Xmx, Node.js--max-old-space-size, PHPmemory_limit) are misconfigured or too high for the allocated container limits. - * Unoptimized Code/Libraries: Using inefficient data structures or libraries that are memory-hungry.
- Host System Memory Exhaustion: While less common when cgroup limits are in place, if the host system itself runs out of memory (e.g., too many containers, other demanding host processes, insufficient swap), the OOM killer might target container processes even if they haven't explicitly hit their Docker limits, or if limits are unset/very high.
- Alpine Linux Specifics: While Alpine's small base image size (often just a few MB) reduces the baseline memory footprint, it doesn't fundamentally change how the OOM killer or cgroups operate. The application inside the Alpine container is still subject to the same memory demands. Often, developers choose Alpine for its small size and then forget to properly account for the application's actual memory needs, leading to hitting tighter limits more quickly.
Step-by-Step Resolution
Resolving OOM issues requires a systematic approach, combining host-level diagnostics, container-level inspection, and application-specific tuning.
1. Confirm OOM Kill and Identify the Victim Process
Before making any changes, confirm the container was indeed OOM killed and identify the exact process within the container that was targeted.
- Check
dmesgoutput on the host: -
`bash - sudo dmesg -T | grep -E "oom-killer|killed process"
-
` - Look for entries similar to the "Symptom & Error Signature" section, specifically mentioning
Memory cgroup out of memoryand the container's cgroup path (e.g.,/docker/a1b2c3d4e5f6). Note thepidandnameof the killed process.
- Check Docker daemon logs:
-
`bash - sudo journalctl -u docker.service | grep "OOM killer killed container"
-
` - This confirms Docker's daemon saw the OOM event.
2. Inspect Container Memory Limits and Usage
Understand what memory limits, if any, were applied to the container, and review historical usage if possible.
- Check configured memory limits:
-
`bash - docker inspect <containeridor_name> | grep -E "Memory|Swap"
-
` - Look for
MemoryandMemorySwapunderHostConfig. Values of0typically mean unlimited, but this is relative to the host's physical memory. - Example output:
-
`json - "Memory": 0,
- "MemorySwap": 0,
- "MemoryReservation": 0,
- "KernelMemory": 0,
- "OomKillDisable": false,
- "OomScoreAdj": 0,
-
` - If
Memoryis0, the container can consume all available host memory, making the host's overall memory the limiting factor. If it's a specific value (e.g.,536870912for 512MB), that's your hard limit.
- Monitor live memory usage (if the container starts but crashes later):
-
`bash - docker stats <containeridor_name>
-
` - This command provides real-time statistics, including memory usage, against the configured limits. Run this frequently to observe memory trends before a crash.
3. Analyze Application Memory Footprint and Configuration
This is often the most critical step. You need to understand how much memory your application genuinely needs.
- Profile the application:
- * Run with generous limits: Temporarily remove or significantly increase memory limits (
-m 2g --memory-swap -1) on a test environment to see how much memory the application consumes at peak load and steady state. - * Use in-container tools: If your Alpine image has tools like
psortop(you might need to installprocps), exec into a running container: -
`bash - docker exec -it <container_id> sh
- / # apk add procps # if not already installed
- / # ps aux
- / # top
-
` - Monitor the
VSZ(virtual size) andRSS(resident set size) columns. - * Language-specific profiling:
- * Java: Tune
JAVA_OPTSwith-Xmx(max heap size) and-Xms(initial heap size). Remember that JVM also uses off-heap memory. - * Node.js: Check for memory leaks using built-in profiling tools or Chrome DevTools. Adjust
--max-old-space-sizeif needed viaNODE_OPTIONS. - * PHP: Adjust
memorylimitinphp.inior viainiset(). - * Python: Libraries like
memory_profilercan help. - * Go: Go applications are often very efficient, but Goroutine leaks can lead to memory growth.
- * Review application logs: Look for errors or warnings that might indicate resource exhaustion, large data operations, or unhandled exceptions that could trigger memory spikes.
4. Adjust Docker Container Memory Limits
Based on your analysis, you will likely need to increase the memory allocated to your container.
- Increase
mem_limit: This directly increases the available RAM. - * Docker CLI:
-
`bash - docker run -d –name my-app –restart always -m 768m my-alpine-app:latest
-
` - This sets the RAM limit to 768MB.
- * Docker Compose: This is the recommended approach for defining services.
-
`yaml - # docker-compose.yml
- version: '3.8'
- services:
- my_app:
- image: my-alpine-app:latest
- container_name: my-app
- restart: always
- mem_limit: 768m # Max RAM usage
- mem_reservation: 512m # Soft limit, Docker tries to keep usage below this
- memswap_limit: 768m # Total RAM + SWAP. Set to -1 for unlimited swap
-
` - > [!IMPORTANT]
- > If
memswaplimitis set to the same value asmemlimit, it means the container has no swap available. If the application hits itsmemlimit, it will immediately be OOM killed. Settingmemswaplimitto-1allows the container to use unlimited swap on the host if itsmemlimitis reached. While this prevents OOM kills, it can lead to severe performance degradation if the application frequently swaps. A better approach is to setmemswaplimithigher thanmem_limit(e.g.,1gfor512mRAM limit) to allow for some controlled swapping.
- Consider
memreservation: This is a "soft limit." Docker will try to keep the container's memory usage below this value, but will allow it to exceed it up tomemlimitif the host has spare memory. This is useful for resource scheduling.
5. Optimize the Application and Dockerfile
Reduce your application's memory footprint or ensure it's configured to respect container limits.
- Application-level tuning:
- * JVM: Reduce
-Xmxif it's too high for the container'smemlimit. Ensure the container'smemlimitis greater than-Xmxto account for off-heap memory. - * Node.js:
NODE_OPTIONS=--max-old-space-size=512(for 512MB) can prevent Node from attempting to use too much memory. - * PHP: Set
memorylimitinphp.inito a value below your Docker container'smemlimit. - * Caching: Implement efficient caching strategies to reduce repetitive memory-intensive operations.
- * Data Structures: Review code for inefficient data structures or algorithms that cause excessive memory allocation.
- * Garbage Collection: Tune garbage collection parameters for languages like Java or Node.js.
- Dockerfile optimization (especially for Alpine):
- * Multi-stage builds: Use multi-stage builds to create extremely lean final images that only contain the necessary runtime dependencies, removing build-time tools and intermediate files.
-
`dockerfile - # Example Multi-stage Dockerfile for Node.js on Alpine
- # Stage 1: Build dependencies
- FROM node:18-alpine AS builder
- WORKDIR /app
- COPY package*.json ./
- RUN npm ci –production –frozen-lockfile
- COPY . .
- RUN npm run build # if you have a build step
Stage 2: Runtime image
FROM node:18-alpine
WORKDIR /app
COPY –from=builder /app/nodemodules ./nodemodules
COPY –from=builder /app/dist ./dist # Assuming build output is in dist
COPY –from=builder /app/package.json ./package.json
COPY –from=builder /app/index.js ./index.js # Your main app file
EXPOSE 3000
CMD ["node", "index.js"]
`
* Minimalist packages: When installing packages with apk add, only install what's absolutely necessary. Use apk add --no-cache to prevent package caches from increasing image size.
6. Increase Host System Memory or Swap (If Necessary)
If multiple containers are consistently hitting OOM issues, or the host itself is frequently low on memory, it might be time to provision more RAM for your server.
- Add more RAM: The most straightforward solution for overall system capacity.
- Increase host swap space: While not a substitute for RAM, sufficient swap space can prevent the host's global OOM killer from being invoked during temporary memory spikes.
-
`bash - > [!IMPORTANT]
- > Relying heavily on swap can severely degrade application performance due to disk I/O. Use it as a last resort or for minor overflow, not as a primary memory extension.
Example: Add a 4GB swap file on Ubuntu/Debian sudo fallocate -l 4G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile
Make swap persistent across reboots by adding to /etc/fstab:
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
`
7. Adjust OOM Score (Advanced – Use with Extreme Caution)
For highly critical containers that must stay alive at all costs (e.g., a monitoring agent), you can adjust their oomscoreadj. A lower score makes the process less likely to be chosen by the OOM killer.
- Docker CLI:
-
`bash - docker run -d –name critical-service –oom-score-adj -500 my-alpine-app:latest
-
` - Values range from -1000 (least likely to be killed) to 1000 (most likely).
[!WARNING] > Lowering the OOM score for a memory-hungry process can lead to the OOM killer targeting other, potentially more critical, system processes or even the Docker daemon itself, potentially destabilizing the host system. Use this only for processes that are truly essential and where all other alternatives for memory optimization and provisioning have been exhausted.
8. Implement Robust Monitoring and Alerting
Proactive monitoring can help identify memory pressure before it leads to a full OOM kill.
- Memory Usage: Monitor container memory usage (RSS, cache, swap) using tools like Prometheus, Grafana, cAdvisor, or specialized APM solutions.
- Container Status: Set up alerts for containers exiting with a
137status. - Host System Metrics: Monitor overall host memory usage, swap usage, and
dmesgoutput for OOM events. - Application Metrics: If your application exposes memory metrics, integrate them into your monitoring stack.
By following these steps, you can effectively diagnose and resolve Docker container OOM issues, ensuring the stability and reliability of your containerized applications on Alpine Linux.
Leave a Reply