Debuggear OOM kills en Docker: guía práctica 2026

Si tenés un contenedor que se cae solo y en los logs aparece OOMKilled, el kernel de Linux lo mató por falta de memoria. Para debuggear OOM kills en Docker el flujo es corto: confirmás el evento con docker events, leés el dmesg para ver qué proceso reventó, frenás el reinicio automático y recién ahí ajustás los límites. Se resuelve en minutos si sabés dónde mirar.

Un OOM kill (Out Of Memory kill) es la acción del kernel de Linux que termina un proceso cuando el sistema (o el cgroup del contenedor) se queda sin memoria disponible. En Docker aparece con el flag OOMKilled: true y el exit code 137. No es un bug de tu app: es el kernel eligiendo a quién sacrificar para no colgar toda la máquina.

En 30 segundos

  • Confirmá el OOM: docker events --filter 'event=die' te muestra el estado OOMKilled del contenedor.
  • Exit code 137 = 128 + 9. El 9 es SIGKILL, la señal que no se puede capturar ni ignorar.
  • Leé el kernel: dmesg | grep -i oom te dice qué proceso murió y cuánta memoria consumía (anon-rss, total-vm).
  • Frená el reinicio con docker update --restart=no para analizar sin que el loop de restarts te tape los logs.
  • Según el análisis publicado el 4 de julio de 2026, una API caída por OOM puede costar cerca de USD 200.000 por minuto.

¿Cómo verifico si mi contenedor Docker fue OOM killed?

Ponele que el servicio se cayó a las 3 de la mañana y no sabés si fue tu código, un deploy o memoria. Lo primero es descartar el OOM, que es el sospechoso más común y el más fácil de confirmar.

El comando directo es este:

  • Eventos recientes: docker events --filter 'event=die' --since '2m' --format '{.Time} {.ID} {.Status}'. Si en la salida ves algo como ...9c2f1a4b5d6e OOMKilled, ya está: fue el killer del kernel.
  • Inspección puntual: docker inspect --format '{.State.OOMKilled}' <container> te devuelve true o false sin ambigüedad.
  • Exit code: docker inspect --format '{.State.ExitCode}' <container>. Si sale 137, es OOM (o alguien mandó un docker kill).

¿Y por qué no alcanza con mirar el log de la app? Porque cuando el kernel manda SIGKILL, tu proceso no llega a escribir nada. Se corta en seco. Por eso el log de aplicación muchas veces termina justo antes del crash, sin ninguna pista. Ahí es donde docker events te salva.

¿Qué comandos dmesg usar para diagnosticar OOM kills en el kernel?

Ya sabés que fue OOM. Ahora la pregunta buena: ¿qué proceso se comió la memoria y cuánta? Eso vive en el ring buffer del kernel, y se lee con dmesg. Sobre eso hablamos en cuando planeas tus pipelines CI/CD.

  • Las últimas líneas de OOM: dmesg | grep -i oom | tail -n 5
  • Con timestamps legibles: dmesg -T | egrep -i 'killed process'

Una salida real del artículo de dev.to se ve así:

[124567.890123] Out of memory: Kill process 3456 (python) score 1128 or sacrifice child
[124567.890124] Killed process 3456 (python) total-vm:102400kB, anon-rss:81920kB, file-rss:0kB

Traducido: el proceso 3456 (un python) tenía un OOM score de 1128 (cuanto más alto, más candidato a morir), reservó unos 100 MB de memoria virtual y tenía 80 MB de memoria anónima residente. Ese anon-rss es el dato clave: es memoria que tu app pidió y usó de verdad, no cache reclamable. Si ese número está cerca de tu límite, encontraste al culpable.

Ojo con una cosa: adentro de muchos contenedores no vas a poder correr dmesg por permisos del namespace. En ese caso lo corrés en el host, no dentro del contenedor.

¿Cómo monitorear OOM kills y memoria en tiempo real?

Diagnosticar después del crash está bien, pero lo ideal es verlo venir. Docker tiene dos herramientas para esto y casi nadie las usa en conjunto.

  • Eventos en vivo: docker events --filter 'event=oom'. Dejalo corriendo en una terminal y te avisa el instante en que el kernel dispara un OOM contra cualquier contenedor.
  • Uso puntual: docker stats mycontainer --no-stream te da un snapshot de MEM USAGE / LIMIT y el porcentaje. Sin el --no-stream queda refrescando en vivo.
  • Detectar fugas: un docker stats mirado a la misma hora durante varios días te muestra si la memoria sube y nunca baja. Eso es un memory leak, no un pico legítimo.

Acá viene lo bueno: la métrica que mirás en docker stats sale del mismo cgroup que evalúa el kernel (memory.current en cgroups v2). O sea, no estás mirando una aproximación. Estás mirando exactamente el número contra el que se compara el límite antes de disparar el OOM.

¿Cuáles son los pasos inmediatos para detener un OOM kill en producción?

La API se cayó, está perdiendo plata por minuto y Docker la está reiniciando en loop. ¿Qué hacés en los primeros dos minutos? No entrás en pánico ni empezás a tocar código. Elige la herramienta de automatización correcta.

El primer reflejo tiene que ser cortar el loop de reinicios:

docker update --restart=no 9c2f1a4b5d6e

¿Por qué frenar el restart si el objetivo es tener el servicio arriba? Porque un contenedor que arranca, se llena de memoria, muere, arranca de nuevo y vuelve a morir te ensucia todos los logs y te impide entender qué pasó. Primero contenés, después recuperás. La secuencia que propone el artículo es clara:

  • Minuto 0-2 (frenar): confirmar el OOM con docker events y desactivar el reinicio automático.
  • Minuto 2-10 (contener y evaluar): juntar el dmesg, revisar el histórico de docker stats y ver si es un pico puntual o una fuga sostenida.
  • Minuto 10 en adelante (recuperar): subir el límite de memoria de forma temporal si hace falta, reiniciar el contenedor ya con el diagnóstico hecho, y recién después planear el fix definitivo.

Subir el límite es un parche, no una cura. Si tu app tiene un leak, más RAM solo estira el tiempo hasta el próximo crash.

¿Qué límites de memoria establecer para prevenir OOM kills?

Acá se pudre casi todo. Mucha gente pone límites al voleo o directamente no pone ninguno, y ahí es donde un contenedor se lleva puesta toda la máquina.

La regla práctica, sacada del uso real: mirá el pico de memoria bajo una prueba de carga y poné el límite con un margen por encima de ese valor. Ni justo (te comés OOM en el primer pico legítimo) ni gigante (perdés la protección que un límite te da).

ConfiguraciónFlagQué haceRecomendación producción
Límite de RAM--memory=512mTecho duro; pasarlo dispara el OOMCon margen por encima del peak observado
Swap--memory-swap=512mIgualarlo a --memory desactiva el swapDesactivar para latencia consistente
Reserva--memory-reservation=384mLímite blando bajo presión de memoriaPor debajo del límite duro
Reinicio--restart=noEvita el loop durante el debugTemporal, solo mientras diagnosticás
debuggear oom kills docker diagrama explicativo

Un error clásico: confundir el límite del cgroup con el límite de heap del lenguaje. Si le das 512 MB al contenedor pero la JVM cree que tiene toda la RAM del host, va a intentar usar de más y el kernel la va a matar antes de que salte cualquier OutOfMemoryError de Java. En Python pasa parecido con procesos que cachean datasets grandes sin liberar. El contenedor tiene que saber su propio límite. Esto se conecta con lo que analizamos en mantén la documentación técnica centralizada.

¿Por qué el exit code 137 es distinto a otros errores de contenedor?

Ves 137 y ya sabés que no fue tu código. El número sale de una cuenta simple: 128 + el número de señal. La señal 9 es SIGKILL, así que 128 + 9 = 137.

SIGKILL tiene una particularidad brutal: no se puede capturar, no se puede ignorar y no se puede manejar. Tu app no tiene forma de correr un handler, hacer flush de los logs ni cerrar conexiones. Por eso un crash por 137 es tan seco y deja tan poco rastro. Comparalo con un exit code 1, que es tu propia app terminando con error y que sí te deja un stack trace o un mensaje. El 137 es el kernel diciendo “se acabó, ahora”.

Por eso .State.ExitCode y .State.OOMKilled se leen juntos: el primero te dice cómo murió (137, SIGKILL), el segundo confirma que fue por memoria y no por un docker kill manual que también da 137.

Errores comunes al debuggear OOM kills en Docker

  • Aumentar la RAM sin diagnosticar: si hay un memory leak, más memoria solo corre la fecha del próximo crash. Primero encontrá por qué sube el consumo, después decidí el límite.
  • Dejar el restart automático durante el debug: el loop de reinicios te tapa los logs y te hace perder el evento original. Frenalo con --restart=no mientras investigás.
  • Correr dmesg adentro del contenedor: el namespace muchas veces no te deja o te muestra info incompleta. El kernel es del host, así que leelo desde el host.
  • Ignorar el anon-rss y mirar solo total-vm: la memoria virtual (total-vm) casi siempre es mucho más grande que la real. El número que importa para el OOM es la memoria residente anónima.

Qué significa para equipos en Latinoamérica

Si corrés tus contenedores en un VPS o servidor cloud, el límite de memoria no es opcional: en instancias chicas un solo contenedor sin techo se lleva puesto todo lo demás, incluida la base de datos. Configurar --memory por contenedor y monitorear con docker stats es la diferencia entre un incidente de un minuto y uno de tres horas. Para infraestructura de hosting y cloud en Argentina, donweb.com es una opción para levantar estos servicios con soporte local.

Preguntas Frecuentes

¿Cómo verifico si mi contenedor fue OOM killed?

Corré docker inspect --format '{.State.OOMKilled}' <container> y si devuelve true, el kernel lo mató por memoria. Alternativa rápida: docker events --filter 'event=die' muestra el estado OOMKilled del contenedor en los eventos recientes.

¿Qué es el exit code 137 en Docker?

El exit code 137 es 128 más 9, donde 9 es la señal SIGKILL. Indica que el proceso fue terminado a la fuerza por el kernel, casi siempre por un OOM kill cuando el contenedor superó su límite de memoria. Tema relacionado: ejecuta procesos locales sin depender de APIs.

¿Cómo leo dmesg para diagnosticar OOM kills?

Usá dmesg | grep -i oom | tail -n 5 desde el host. La salida te muestra qué proceso murió, su OOM score y su consumo real de memoria en las métricas anon-rss y total-vm.

¿Qué hace docker update –restart=no y cuándo lo uso?

Desactiva la política de reinicio automático de un contenedor en caliente, sin recrearlo. Se usa durante un incidente de OOM para cortar el loop de reinicios y poder analizar los logs y el estado sin ruido.

¿Sirve activar swap en contenedores de producción?

En general no, porque el swap introduce latencia impredecible bajo carga. Para producción se recomienda igualar --memory-swap a --memory, lo que desactiva el swap y fuerza un comportamiento de memoria consistente.

Conclusión

Un OOM kill parece un misterio hasta que sabés la secuencia. Confirmás con docker events o el flag OOMKilled, leés el dmesg para encontrar el proceso y su anon-rss, frenás el reinicio automático y ajustás el límite con margen sobre el peak real. El exit code 137 deja de ser un jeroglífico y pasa a ser un diagnóstico.

Lo que cambia es el enfoque: dejás de reaccionar al crash y empezás a prevenirlo con límites bien puestos y docker stats corriendo. Si manejás servicios que no pueden caerse, configurá los límites hoy y dejá un docker events --filter 'event=oom' vigilando. El próximo pico de memoria te va a avisar antes de costarte plata.

Fuentes

Te puede interesar...