Fix: Docker exited with code 137 on low-memory ARM instances

Root Cause Diagnosis

Docker exit code 137 indicates that a process inside the container was forcefully terminated by the Linux kernel’s Out-Of-Memory (OOM) Killer (SIGKILL signal 9 + offset 128). On low-memory ARM architectures (such as AWS Graviton t4g.nano/micro or Raspberry Pi 4 with 1GB–2GB RAM), page size differences (e.g., 64KB pages vs standard 4KB pages on x86) and higher memory overhead per allocation accelerate memory exhaustion under heavy I/O or multi-threaded build workloads. Without explicit swap space or container memory limits, system RAM exhausts quickly during intensive operations like npm build or GCC/Clang compilation, causing the kernel OOM killer to select the main Docker process.

Quick Fix

Create and enable a 2GB swap file immediately to prevent the host kernel from invoking the OOM killer on container processes:

sudo fallocate -l 2G /swapfile || sudo dd if=/dev/zero of=/swapfile bs=1M count=2048
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

Verify swap is active:

free -h

Step-by-Step Resolution

Step 1: Make Swap Allocation Persistent

Ensure the swap space survives host reboots by updating /etc/fstab.

  1. Open the system file:sudo nano /etc/fstab
  2. Append the following entry to the end of the file:/swapfile swap swap defaults 0 0

Step 2: Configure System Swappiness and Cache Pressure

Tune kernel memory management parameters in /etc/sysctl.conf to prioritize swap usage without causing severe thrashing on slow flash or EBS storage.

  1. Edit system runtime parameters:sudo nano /etc/sysctl.conf
  2. Add or update the following values:vm.swappiness=20 vm.vfs_cache_pressure=50
  3. Apply settings immediately:sudo sysctl -p

Step 3: Enforce Docker Resource Limits

Prevent a single container from consuming all system RAM and triggering host-wide memory panics.

See also  How to Fix journalctl Logs Consuming 100% Disk Space on Ubuntu Servers

Option A: Docker Compose

Add mem_limit or deployment resource constraints in docker-compose.yml:

version: '3.8'

services:
  app:
    image: my-arm-app:latest
    deploy:
      resources:
        limits:
          memory: 768M
        reservations:
          memory: 256M
    # Optional fallback for v2 compose syntax:
    # mem_limit: 768m

Option B: Docker CLI

Pass memory and swap parameters explicitly in the docker run command:

docker run -d \
  --name arm_app \
  --memory="768m" \
  --memory-swap="1536m" \
  my-arm-app:latest

Step 4: Limit Parallel Build Jobs (For Compiler/Build OOM Outages)

If Exit Code 137 occurs during build steps (e.g., docker build), restrict multi-core parallel worker instances that consume memory exponentially on ARM nodes.

  • Node.js/npm: Set allocation flags in your Dockerfile:ENV NODE_OPTIONS="--max-old-space-size=512"
  • Make/C++/Rust Compilation: Restrict build threads in your Dockerfile:RUN make -j2 # Limit to 2 concurrent jobs instead of -j$(nproc)

Why This Fails for Some Users

  • Btrfs or ZFS Filesystems: fallocate fails on filesystems with copy-on-write (CoW) enabled (such as Btrfs on Fedora/Arch ARM images). You must use dd to create the zero-filled file directly, disable CoW using chattr +C /swapfile, and format before enabling.
  • Kernel Page Size Mismatch (64KB vs 4KB): Certain ARM kernel distributions (e.g., RHEL/CentOS on Graviton, Ubuntu 64k kernel variants) use a 64KB memory page size instead of 4KB. Memory allocations are rounded up to the nearest page size, increasing internal fragmentation and baseline RAM utilization by up to 20-30%.
  • cgroup v2 Swap Controller Disabled: On Debian and Ubuntu ARM releases, cgroup memory swap accounting might be disabled by default in boot flags. If docker run --memory-swap produces a warning or fails, enable it in /etc/default/grub:GRUB_CMDLINE_LINUX_DEFAULT="systemd.unified_cgroup_hierarchy=1 cgroup_enable=memory swapaccount=1" Run sudo update-grub and reboot the host.
  • AWS EBS Burst Credit Exhaustion: Creating swap on burstable storage (e.g., gp2/gp3 on EC2 instances) can lead to severe I/O throttling once burst credits deplete. If the kernel waits too long for swap I/O completion, the container may stall or trigger host-leve
See also  Docker Compose Network: "Port is Already Allocated" Fix

Leave a Comment