RAPID INCIDENT TRIAGE & ACTION PLAN•Performance
01. Error Signature
Docker Cgroups Kernel OOM Monitor
02. Root Cause Vector

The Linux kernel OOM killer terminated your container after it breached its cgroup memory hard limit, causing immediate process death and...

03. Rapid Prescription

Increase container memory limits or optimize runtime heap/cgroup footprint.

Zero-Trust Client IDE/ Performance

Docker Cgroups Kernel OOM Monitor

The Linux kernel OOM killer terminated your container after it breached its cgroup memory hard limit, causing immediate process death and potential data loss.

SYSTEM STATUS
CREDITS: 0 / 3
source.yaml(39L)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# TODO: This Dockerfile is a mess after the PCI migration. Needs refactoring ASAP.
# Legacy image from internal registry for compliance reasons.
FROM my-registry.internal/legacy-api:ubuntu-18.04-patched

# Old environment variables from the monolith days. Some might be dead.
ENV APP_HOME=/var/www/legacy-app \
    LOG_LEVEL=debug \
    MAX_UPLOAD_SIZE=10G  # This is probably insane, but the business unit insists.

# Workaround for the network timeout issue in the PCI-secure-zone.
# RUN echo "nameserver 10.10.10.10" > /etc/resolv.conf

# Install dependencies. The apt-get update is flaky on our internal mirror.
RUN apt-get update && apt-get install -y \
    python3.7 \
    python3-pip \
    # libssl1.0.0  # Commented out after the Heartbleed scare, but might be needed.
    default-jre  # Why is Java here? Nobody knows. Legacy contract.

# Copy application code. The ./src might be huge.
COPY ./src $APP_HOME
WORKDIR $APP_HOME

# Embedding some configs with secrets (rotated last year, probably stale).
# VULNERABILITY: The memory limit here (100m) is set, but the JVM heap below is configured for 512m via environment, causing the container to exceed its cgroup limit and be killed by the OOM killer.
RUN echo 'DB_URI="postgresql://prod-user:AHx7!9fL7q#2@sZ@pci-db-primary.internal:5432/transactions?sslmode=require"' > .env.secret
RUN echo 'AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE' >> .env.secret
RUN echo 'AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' >> .env.secret
RUN echo 'STRIPE_API_KEY=sk_live_4eC39HqLyjWDarjtT1zdp7dc' >> .env.secret

# Multi-line RSA key for the old SFTP batch job (should be in a secret manager).
RUN echo '-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEArX7C7g5Q... (truncated for brevity) ...\n-----END RSA PRIVATE KEY-----' > /root/.ssh/id_rsa

# Setting JVM options. The ops team said to set this for performance.
ENV JAVA_OPTS="-Xmx512m -Xms256m"

# The memory limit is set in the docker-compose.yml, but this Dockerfile doesn't know about it.
# This is the entrypoint for the legacy Java/Python hybrid app.
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar legacy-app.jar & python3 app.py"]
AI Audit Console
> Waiting for pre-flight audit...
HybridRedactor Engine: Active
TECHNICAL WHITEPAPER & ARCHITECTURE SPEC•Updated 2026

How to Fix Docker cgroups Memory Limit OOM Kills: Diagnosing and Resolving Kernel Container Terminations

Threat/Impact Level: CRITICAL | Exploitability/Downtime Risk: HIGH | Time to Fix: 15 mins

TL;DR

  • What broke: The Linux kernel's cgroup v1/v2 memory subsystem enforced a hard memory.limit_in_bytes on your container. The process exceeded it. The kernel OOM killer sent SIGKILL. Container is dead.
  • How to fix it: Either raise --memory to match actual workload RSS, or tune the application's internal heap/memory settings to stay under the existing limit. Add a soft limit (--memory-reservation) to get early warnings.
  • Shortcut: Use our Client-Side Sandbox above to auto-refactor your Docker Compose or docker run flags against this exact OOM scenario.

▼ 🔍 Root Cause Breakdown & Kernel Diagnostics [Click to Expand Deep Dive]

The Incident (What does the error mean?)

Your kernel logs and Docker daemon surface this as:

KERNEL: Memory cgroup out of memory: Kill process 14821 (java) score 1823 or sacrifice child
Killed process 14821 (java) total-vm:4194304kB, anon-rss:2097152kB, file-rss:32768kB
docker: container killed by kernel OOM killer
exited with code 137

Exit code 137 = 128 + 9 (SIGKILL). This is not a graceful shutdown. There is no cleanup, no connection draining, no checkpoint. The process is instantly terminated by the kernel at the moment cgroup memory.usage_in_bytes attempts to exceed memory.limit_in_bytes. Any in-flight transactions, open file handles, or write buffers are gone.

Verify the active cgroup limit on a running container:

# Get the cgroup path
docker inspect <container_id> --format '{{.HostConfig.Memory}}'

# Read directly from cgroup v1
cat /sys/fs/cgroup/memory/docker/<container_id>/memory.limit_in_bytes

# cgroup v2
cat /sys/fs/cgroup/system.slice/docker-<container_id>.scope/memory.max

# Watch OOM events in real time
dmesg -w | grep -i 'oom\|killed'

▼ 🔍 Detailed Impact & Blast Radius Analysis [Click to Expand Deep Dive]

The Attack Vector / Blast Radius

This is a cascading failure vector, not an isolated container death.

  1. Kubernetes restart loops: If this is a K8s pod, OOMKilled status triggers CrashLoopBackOff. The pod restarts with exponential backoff. Under sustained load, it never recovers. Your Deployment is effectively down.
  2. Data corruption: Any container writing to a database, message queue, or file without fsync guarantees loses buffered writes. PostgreSQL connections are severed mid-transaction. Kafka producers lose unacknowledged messages.
  3. Noisy neighbor amplification: On a shared node, if --memory-swap is not explicitly set, Docker defaults to 2x the memory limit as swap. The container starts thrashing swap before the OOM kill, degrading every other container on the host for minutes before the kill fires.
  4. Silent recurrence: Without --restart=on-failure or proper liveness probes, the container stays dead. Monitoring that only checks HTTP endpoints misses this entirely if the port never re-binds.
  5. JVM/Node.js specific trap: The JVM does not respect cgroup limits by default below JDK 8u191. It reads /proc/meminfo for total host RAM and sets heap accordingly. A container with --memory=512m on a 64GB host will have the JVM attempt a multi-GB heap. It will be killed every time.

How to Fix It

Basic Fix — Raise the Limit to Match Actual RSS

First, profile actual usage before changing anything:

# Peak RSS of the container over 60 seconds
docker stats <container_id> --no-stream --format "table {{.MemUsage}}\t{{.MemPerc}}"
# docker run flags
- docker run --memory=256m --memory-swap=256m myapp:latest
+ docker run --memory=768m --memory-swap=1g --memory-reservation=512m myapp:latest
# docker-compose.yml
services:
  app:
    image: myapp:latest
    deploy:
      resources:
        limits:
-         memory: 256M
+         memory: 768M
        reservations:
-         memory: 128M
+         memory: 512M

--memory-reservation (soft limit): This does not kill the container. It signals the kernel to reclaim memory from this container first under host memory pressure. Use it as your early-warning threshold — set it to ~70% of the hard limit.


Enterprise Best Practice — Fix the Application, Not Just the Limit

Raising limits without fixing the root cause is kicking the can. The correct fix is constraining the application's internal allocator to stay under the cgroup ceiling.

JVM (Java/Scala/Kotlin) — the most common offender:

# Dockerfile or entrypoint
- CMD ["java", "-jar", "app.jar"]
+ CMD ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-XX:InitialRAMPercentage=50.0", "-XX:+ExitOnOutOfMemoryError", "-jar", "app.jar"]

-XX:+UseContainerSupport (JDK 8u191+, JDK 10+) makes the JVM read cgroup limits instead of host /proc/meminfo. -XX:MaxRAMPercentage=75.0 caps heap at 75% of the cgroup limit, leaving headroom for metaspace, native threads, and off-heap buffers. -XX:+ExitOnOutOfMemoryError gives you a clean exit code instead of a hung, zombie JVM.

Node.js:

- CMD ["node", "server.js"]
+ CMD ["node", "--max-old-space-size=400", "server.js"]

For a 512MB container, --max-old-space-size=400 (in MB) caps V8's old generation heap, leaving ~100MB for OS, native modules, and Buffer allocations.

Kubernetes — set both requests and limits, never only limits:

resources:
- limits:
-   memory: "512Mi"
+ requests:
+   memory: "384Mi"
+ limits:
+   memory: "512Mi"

Without requests, the scheduler places the pod on any node regardless of available memory. The pod gets scheduled onto a saturated node and OOMs immediately on startup.


💡 Tired of pasting proprietary infrastructure configs into public LLMs? Generic AI interfaces log your company's ARNs, connection strings, and private keys. StackEngine enforces client-side zero-trust boundaries—the sandbox above executes 100% locally in your browser. Drop your broken manifest above to redact secrets locally and synthesize deterministic patch drafts with your own API key.

(For CI/CD pipeline automation, install via npm i -g stackengine).


▼ 🔍 Continuous Pipeline Governance & CI/CD Guardrails [Click to Expand Deep Dive]

Prevention in CI/CD

The goal is catching under-provisioned memory limits before they hit production.

1. Checkov — static analysis on Compose and K8s manifests:

pip install checkov
checkov -f docker-compose.yml --check CKV_DOCKER_7,CKV_DOCKER_14
checkov -d ./k8s-manifests/ --check CKV_K8S_11,CKV_K8S_13

CKV_K8S_11 flags missing memory limits. CKV_K8S_13 flags missing memory requests.

2. OPA/Gatekeeper — enforce memory limits at admission:

# Deny pods with no memory limit set
violation[{"msg": msg}] {
  container := input.review.object.spec.containers[_]
  not container.resources.limits.memory
  msg := sprintf("Container '%v' has no memory limit. OOM kills will be uncontrolled.", [container.name])
}

3. Load test with memory profiling in staging — not optional:

# Run your load test, then capture peak memory
docker stats --format "{{.MemUsage}}" <container_id> > mem_profile.log &
k6 run --vus 50 --duration 5m load_test.js

Set your --memory limit to peak observed RSS + 25% headroom. Never guess.

4. Alerting — alert on container_memory_working_set_bytes before the kill:

# Prometheus alert rule
- alert: ContainerMemoryNearLimit
  expr: |
    (container_memory_working_set_bytes / container_spec_memory_limit_bytes) > 0.85
  for: 2m
  labels:
    severity: warning
  annotations:
    summary: "Container {{ $labels.container }} at {{ $value | humanizePercentage }} of memory limit"

Firing at 85% gives you time to act before the kernel fires at 100%.

Official Grounding & Normative Specifications

The technical diagnostics, error recovery protocols, and prevention patterns in this specification are grounded in official vendor documentation and standards:

Diagnostic & Architectural FAQ

Q1.What does exit code 137 mean in Docker?
Exit code 137 is the kernel's SIGKILL, a brutal termination where the Linux OOM killer annihilates your container for breaching its cgroup memory limit—no cleanup, no warnings. This is a fundamental failure of your container's memory governance, exposing a critical gap in your runtime security posture where resource constraints are enforced by termination, not prevention.
Q2.How do I tell if my container was killed by OOM and not a crash?
You can manually scour `docker inspect`, `dmesg`, or Kubernetes events to confirm the OOM kill, but this reactive forensics is a high-risk trap. Every second spent diagnosing is a second your production environment runs blind to the next imminent kill. Manual patching of memory limits or JVM flags is error-prone and creates security vulnerabilities through misconfigured resource boundaries. Instead, drop the broken manifest into the StackEngine Local Sandbox above to automatically compile a zero-trust fix that enforces correct, secure memory policies before deployment.
Q3.Why does my JVM container always get OOM killed even when I set a memory limit?
Legacy JVMs ignore cgroup limits, blindly allocating based on host memory—a catastrophic compliance failure in containerized environments. While upgrading JDK and applying flags like `-XX:+UseContainerSupport` is a tactical fix, managing this across large mono-repos or at scale is a compliance nightmare, inviting drift and audit failures. For enterprise-grade, automated PR drafting under Maker-Checker review, upgrade to the StackEngine CI/CD Automator (Team Edition) to enforce these policies on-prem across your entire CI/CD pipeline, ensuring consistent, auditable security from code to cloud.