Cambiar tema

Guía completa de límites de recursos en Docker: evita que una fuga de memoria tumbe el servidor

Easton editorial illustration: developer problem-solving desk

Suena la alarma del móvil: «servidor sin respuesta», «CPU al 100 %», «timeout de SSH». Te conectas por VPN tres veces antes de lograrlo, escribes ssh — timeout. El servidor está colgado.

Tras un reinicio forzado, los logs cuentan la historia: un contenedor que llevaba seis meses en marcha con fuga de memoria, de 500MB a 16GB, agotó toda la RAM del host; ni SSH tenía recursos para responder. Otros tres contenedores sanos cayeron con él — producción en llamas. En 2024, Docker 27.0.3 tuvo un bug grave de fuga de memoria que acabó con 68 contenedores de un solo golpe por el OOM Killer.

16GB
Fuga de memoria
Una fuga de memoria en un contenedor pasó de 500MB a 16GB y agotó toda la RAM del servidor

Este artículo resuelve una cosa: cómo evitar que un solo contenedor tumbe todo el servidor. Desde los cgroups en profundidad hasta --memory y --cpus en la práctica, y luego docker stats, cAdvisor y Prometheus, cubrimos los límites de recursos en Docker de punta a punta. Al terminar, al menos sabrás hacer que el contenedor caiga solo cuando aparezca una fuga de memoria, en lugar de sacrificar todo el host.

¿Por qué un contenedor puede tumbar el servidor?

Docker tiene una «característica»: por defecto, los contenedores no tienen límite de recursos. Suena liberador, ¿verdad? Tan liberador que un contenedor puede consumir toda la memoria y la CPU del host, y todos caen juntos.

Caso real con Docker 27.4.0: un usuario vio que dockerd pasó de unos cientos de MB a 8GB en pocos días, dejando el servidor casi inutilizable. No es que Docker sea inestable — el problema es que, si no pones límites, el contenedor es un caballo desbocado: consume lo que quiera.

Entonces el kernel de Linux activa un «asesino»: el OOM Killer (Out Of Memory Killer). Cuando la memoria se agota, elige el proceso «más adecuado» para matar y liberar RAM. ¿Cómo elige? Asigna una puntuación (oom_score) a cada proceso; cuanto más alta, más probable la muerte. Los procesos dentro de contenedores suelen puntuar alto, pero el daemon de Docker es más astuto y baja su prioridad OOM (oom_score_adj a -500), así que suelen morir tus contenedores.

En docker ps el contenedor desaparece; en los logs verás Exit Code 137. Eso significa: 128 + 9 (señal SIGKILL), en otras palabras, «asesinado a la fuerza». No es una salida limpia: es un corte del kernel.

Los síntomas de una fuga de memoria suelen ser estos:

  • El uso de memoria del contenedor sube como un cohete, de cientos de MB a varios GB sin parar
  • El servidor empieza a usar swap a lo loco; el disco parpadea sin descanso
  • Otros contenedores responden cada vez más lento y acaban cayendo
  • En tus gráficos de monitorización aparece una bonita recta ascendente

En 2024 hubo un caso llamativo en la comunidad Storj: un contenedor pasó de unos cientos de MB normales a 37GB y casi tumbó todo el nodo de almacenamiento. Con un límite de memoria de 1GB, el OOM Killer lo habría eliminado a tiempo y el servidor habría seguido estable.

[Imagen: gráfico de curva de fuga de memoria]
Prompt: server memory usage graph showing sharp upward spike, red critical zone at 90%, dark background, monitoring dashboard style, high quality

cgroups: el fundamento de los límites de recursos

Seguro que has oído que Docker usa «cgroups» para limitar recursos. ¿Qué son? En resumen, cgroups (Control Groups) es una función del kernel de Linux que asigna cuotas de recursos a grupos de procesos — como una «tarjeta de recursos» por proceso: cuando se agota la cuota, no hay más crédito.

Al crear un contenedor, Docker crea un cgroup y mete ahí sus procesos. Cuando ejecutas docker run -m 512m nginx, Docker escribe en /sys/fs/cgroup/memory/docker/<containerID>/memory.limit_in_bytes el valor 536870912 (512MB en bytes). El kernel lo lee y dice: este contenedor como máximo 512MB; si se pasa, fuera.

cgroups v1 frente a v2:

  • v1: memoria, CPU y E/S de disco en subsistemas separados, cada uno por su lado
  • v2: gestión unificada, jerarquía más clara, mejor para escenarios como contenedores que necesitan control global
  • Sistemas antiguos (p. ej. RHEL 7) siguen en v1; los nuevos (Ubuntu 20.04+, RHEL 8+) usan v2

Si quieres ver la configuración real del cgroup de un contenedor:

# Obtener el ID completo del contenedor
docker inspect --format='{{.Id}}' my_container

# Ver límite de memoria (cgroups v1)
cat /sys/fs/cgroup/memory/docker/<containerID>/memory.limit_in_bytes

# Ver cuota de CPU (cgroups v1)
cat /sys/fs/cgroup/cpu/docker/<containerID>/cpu.cfs_quota_us

La primera vez que vi esos archivos me descolocó: ¿los límites se configuran con el sistema de archivos? Luego entendí la filosofía «todo es un archivo» de Linux: el kernel expone la configuración de cgroups como archivos; Docker escribe valores y el kernel aplica los límites. Diseño elegante.

[Imagen: diagrama de jerarquía de cgroups]
Prompt: Linux cgroups hierarchy diagram, containers grouped under docker cgroup, memory and CPU subsystems, tree structure, technical illustration, clean design, high quality

Parámetros de límite de memoria al detalle

Docker ofrece muchos parámetros de memoria, pero en la práctica solo importan unos pocos. Los repasamos uno a uno.

1. --memory / -m (límite duro, el más crítico)

Es el parámetro que salva vidas. Cuando el contenedor alcanza este valor, el OOM Killer actúa y el contenedor muere. El mínimo es 6MB (casi inútil para algo serio); en producción suele empezarse en cientos de MB.

# Limitar el contenedor a 512MB de memoria como máximo
docker run -m 512m nginx

# También puedes usar GB
docker run -m 2g my-app

¿Cómo elegir el valor? Mi regla: haz pruebas de carga y mira el uso normal de la app; multiplica por 1,2–1,5. Si usa 300MB en reposo, 400–450MB suele ir bien. Muy bajo y el contenedor muere a menudo; muy alto y pierdes protección.

2. --memory-swap (espacio de intercambio, fácil de malinterpretar)

Este parámetro confunde a muchos. Define el total memoria + swap, no el tamaño de swap por separado.

# 512MB de memoria + 512MB de swap (1GB en total)
docker run -m 512m --memory-swap 1g nginx

# Desactivar swap (solo memoria)
docker run -m 512m --memory-swap 512m nginx

# Swap ilimitado (¡peligroso!)
docker run -m 512m --memory-swap -1 nginx

Si no defines --memory-swap, por defecto swap = memory: el total disponible es el doble de memory. Con -m 512m, el contenedor puede usar hasta 1GB (512m RAM + 512m swap).

Recomendación en producción: desactiva swap (--memory-swap igual a --memory) o limita swap a la mitad de memory como máximo. No uses -1: un contenedor abusando de swap puede matar el disco.

3. --memory-reservation (límite blando)

Es una «cuota flexible». Con memoria de sobra en el host, el contenedor puede superar este valor; bajo presión, el kernel intenta mantenerlo por debajo. Debe ser menor que --memory.

# Límite blando 750MB, límite duro 1GB
docker run -m 1g --memory-reservation 750m nginx

Útil cuando la app a veces pide mucha memoria de golpe (p. ej. batch) pero en reposo consume poco.

4. --kernel-memory (memoria del kernel, usar con cuidado)

Limita la memoria del kernel usada por el contenedor (buffers de red, caché de sistema de archivos, etc.); esa memoria no puede ir a swap. En serio: si no sabes exactamente qué haces, no lo toques — puedes impedir que el contenedor arranque.

5. --oom-kill-disable (parámetro peligroso, atención)

Desactiva el OOM Killer para que el contenedor no muera al superar memoria. Suena bien, pero es un error grave. Si la memoria se descontrola y no hay kill, el host entero se queda sin RAM.

# ¡Así se rompe un servidor! (memoria sin límite efectivo)
docker run --oom-kill-disable nginx

# Si lo usas, combínalo siempre con límite de memoria
docker run -m 512m --oom-kill-disable nginx

¿Cuándo usarlo? Casi nunca. Solo si una app muy especial no puede ser matada de repente (p. ej. checkpoint de base de datos) y garantizas control de memoria en la aplicación.

Caso real: en AWS un usuario tumbó una instancia EC2; tras mucho rastreo, un contenedor sin límite de memoria llegó a 30GB y dejó la instancia sin respuesta. Con -m 2g, al tocar el límite el contenedor cae y reinicia, y el servidor sigue estable.

[Imagen: diagrama de relación entre parámetros de memoria]
Prompt: Docker memory parameters diagram, showing memory and swap relationship, visual chart with bars and labels, technical illustration, blue and orange colors, high quality

Parámetros de límite de CPU al detalle

Los límites de CPU son más «suaves» que los de memoria: no matan el proceso, solo lo frenan. Pero una CPU descontrolada también puede dejar el servidor inutilizable.

1. --cpus (la forma más intuitiva)

Indica cuántos núcleos de CPU puede usar el contenedor; admite decimales.

# Como máximo 1,5 núcleos de CPU
docker run --cpus="1.5" nginx

# Solo medio núcleo
docker run --cpus="0.5" my-app

Por debajo usa --cpu-period y --cpu-quota; Docker calcula la proporción. Con 1,5, el contenedor puede usar como máximo la capacidad de 1,5 núcleos — un núcleo al 100 % más otro al 50 %.

2. --cpu-shares (peso relativo, no límite duro)

Define la prioridad en la planificación de CPU; el valor por defecto es 1024. Clave: solo importa cuando la CPU está saturada. Si el host tiene CPU libre, el contenedor puede usar lo que necesite.

# El contenedor A recibe el doble de tiempo de CPU que B
docker run --cpu-shares 2048 --name app_a my-app
docker run --cpu-shares 1024 --name app_b my-app

Con 2 núcleos al 100 %, A y B se reparten en proporción 2:1 — A ~1,33 núcleos, B ~0,67. Con CPU holgada, ambos pueden ir a toda velocidad.

¿Cuándo usarlo? Varios contenedores y quieres priorizar un servicio crítico (p. ej. API frente a tareas en segundo plano).

3. --cpuset-cpus (anclar a núcleos concretos)

Fija el contenedor a ciertos núcleos; el resto queda fuera.

# Solo los núcleos 0 y 3
docker run --cpuset-cpus="0,3" nginx

# Núcleos del 2 al 5
docker run --cpuset-cpus="2-5" my-app

Casos de uso:

  • Arquitectura NUMA: en servidores multi-socket, anclar al mismo socket reduce accesos remotos a memoria
  • Evitar invalidación de caché: procesos en núcleos fijos mejoran la tasa de acierto de caché
  • Aislar servicios críticos: núcleos dedicados para cargas importantes

En un entorno Kubernetes vi anclar la base de datos a los 4 últimos núcleos de un host de 8 y dejar los 4 primeros al servicio web; la mejora fue notable.

4. --cpu-period y --cpu-quota (control fino)

Son parámetros de bajo nivel; --cpus los encapsula.

  • --cpu-period: periodo del planificador CFS, por defecto 100000 microsegundos (100 ms)
  • --cpu-quota: microsegundos de CPU permitidos por periodo
# En 100 ms, solo 50 ms de CPU (equivalente a 0,5 núcleos)
docker run --cpu-period=100000 --cpu-quota=50000 nginx

# Equivalente a
docker run --cpus="0.5" nginx

En la mayoría de casos basta --cpus; los parámetros finos sirven si necesitas periodos más cortos y planificación más frecuente.

Caso real: un bug dejó un pool de hilos en bucle infinito; la CPU del contenedor llegó al 800 % (host de 8 núcleos). Sin límites, todo el servidor colapsó y ni SSH respondía. Tras poner --cpus="2" en todos los contenedores, un bug similar solo ralentiza al culpable; el resto sigue estable.

[Imagen: comparación de pruebas de límite de CPU]
Prompt: CPU usage comparison chart, with and without limits, before/after graph showing CPU spike prevention, performance monitoring dashboard, clean visualization, high quality

Plan de monitorización en la práctica

Poner límites es solo el primer paso; también debes saber cuánto consume cada contenedor. Si descubres la fuga cuando ya disparó el OOM, vas tarde.

Herramienta 1: docker stats (integrada, coste cero)

Lo más simple; viene con Docker.

# Actualización en tiempo real; Ctrl+C para salir
docker stats

# Una sola salida, útil en scripts
docker stats --no-stream

# Solo contenedores concretos
docker stats nginx_container mysql_container

La salida se ve así:

CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT     MEM %     NET I/O
a1b2c3d4e5f6   nginx     0.50%     45.2MiB / 512MiB     8.83%     1.2kB / 0B

Ventajas: listo para usar, sin instalación.
Inconvenientes: solo estado actual, sin histórico, sin alertas ni visualización rica. Sirve para diagnóstico puntual, no para monitorización a largo plazo.

Herramienta 2: cAdvisor (de Google, experto en contenedores)

cAdvisor (Container Advisor) detecta todos los contenedores del host, recoge CPU, memoria, red, E/S de disco, ofrece interfaz web y endpoint de métricas en formato Prometheus.

Arranque sencillo:

docker run -d \
  --name=cadvisor \
  --restart=always \
  -p 8080:8080 \
  -v /:/rootfs:ro \
  -v /var/run:/var/run:ro \
  -v /sys:/sys:ro \
  -v /var/lib/docker/:/var/lib/docker:ro \
  -v /dev/disk/:/dev/disk:ro \
  gcr.io/cadvisor/cadvisor:latest

Tras el arranque, http://IP-del-servidor:8080 muestra tendencias por contenedor; http://IP-del-servidor:8080/metrics expone datos para Prometheus.

Ventajas: completo, integrable con Prometheus.
Inconvenientes: retiene solo ~2 minutos de datos; para histórico necesitas Prometheus.

Herramienta 3: Prometheus + Grafana (esquema de nivel empresarial)

Stack completo:

  • cAdvisor: recolección de métricas de contenedores
  • Prometheus: scrape y almacenamiento
  • Grafana: visualización y alertas

Configuración completa en docker-compose.yml:

version: '3.8'

services:
  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    container_name: cadvisor
    ports:
      - "8080:8080"
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
      - /dev/disk/:/dev/disk:ro
    restart: always

  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
    restart: always

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    restart: always

volumes:
  prometheus_data:
  grafana_data:

prometheus.yml de apoyo:

global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'cadvisor'
    static_configs:
      - targets: ['cadvisor:8080']

Tras el despliegue:

  1. Prometheus scrapea cAdvisor cada 15 segundos
  2. Grafana en http://IP-del-servidor:3000 (admin/admin por defecto)
  3. Añade Prometheus como fuente de datos (http://prometheus:9090)
  4. Importa un dashboard (recomendado ID 19908, pensado para contenedores Docker)

Métricas clave:

  • container_memory_usage_bytes: memoria actual del contenedor
  • container_memory_max_usage_bytes: pico histórico de memoria
  • container_cpu_load_average_10s: carga media de CPU a 10 s
  • container_fs_io_time_seconds_total: tiempo de E/S en disco

Configura una alerta cuando el uso de memoria supere el 80 % (email, Slack, etc.) y detectarás el problema antes de que explote.

Montar todo lleva trabajo, pero compensa. Yo monitorizo más de 20 contenedores con Grafana y hace tiempo que no me despiertan a las 3 de la madrugada.

[Imagen: panel de monitorización de contenedores en Grafana]
Prompt: Grafana dashboard showing Docker container metrics, memory and CPU graphs, clean modern UI, dark theme, monitoring panels with colorful charts, high quality

Flujo completo para diagnosticar fugas de memoria

Suena la alerta o el contenedor cae sin aviso: sigue este flujo para localizar el problema rápido.

Paso 1: detectar la anomalía

Primero, docker stats para ver qué contenedor dispara la memoria:

docker stats --no-stream | grep -v "0.00%"

¿Un contenedor cerca del límite? Comprueba si el OOM Killer actuó:

# Eventos OOM del contenedor
docker events --filter 'event=oom' --since '24h'

# Código de salida del contenedor
docker inspect <nombre-contenedor> --format='{{.State.ExitCode}}'
# Si devuelve 137, el OOM Killer lo mató

Paso 2: analizar el uso de memoria

Entra al contenedor y mira qué proceso consume:

# Entrar al contenedor
docker exec -it <nombre-contenedor> /bin/bash

# Ranking por memoria (si hay top/htop)
top -o %MEM

# O con ps
ps aux --sort=-%mem | head -n 10

En apps Java, puedes exportar un heap dump:

# PID del proceso Java
jps

# Exportar heap dump
jcmd <PID> GC.heap_dump /tmp/heap.hprof

# Copiar el archivo fuera
docker cp <nombre-contenedor>:/tmp/heap.hprof ./

Paso 3: respuesta de emergencia

Si el servicio ya está afectado, alivia de inmediato:

# Reiniciar el contenedor (pierdes datos temporales dentro)
docker restart <nombre-contenedor>

# Si sigue en marcha, ajustar límite de memoria al vuelo
docker update --memory 1g --memory-swap 1g <nombre-contenedor>

# Limpiar recursos no usados (cuidado: borra imágenes y contenedores huérfanos)
docker system prune -a

Paso 4: solución de fondo

Los parches temporales solo contienen la hemorragia; para curar:

  1. Corregir el código: localizar la fuga (conexiones sin cerrar, caché sin límite, objetos grandes retenidos, etc.)
  2. Poner límites: si aún no los tienes, añade --memory ya
  3. Desplegar monitorización: Prometheus+Grafana para alertar antes del desastre
  4. Reinicio automático: --restart=on-failure:3, hasta 3 reinicios tras OOM

Lista rápida de comandos:

# Límites configurados en todos los contenedores
docker ps --format "{{.Names}}" | xargs docker inspect \
  --format='{{.Name}}: Memory={{.HostConfig.Memory}} CPU={{.HostConfig.NanoCpus}}'

# Historial de reinicios del contenedor
docker inspect --format='{{.RestartCount}}' <nombre-contenedor>

# Últimas 100 líneas de log
docker logs --tail 100 <nombre-contenedor>

# Detalle de uso de memoria
docker stats --no-stream --format \
  "table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}" <nombre-contenedor>

Repaso del caso Storj (37GB): el usuario hizo esto:

  1. Tras ver la subida de memoria, docker restart para recuperar el servicio
  2. Añadió -m 1g para no volver a tumbar el host
  3. Exportó logs y heap dump al equipo de desarrollo
  4. Tras el parche, actualizó la imagen y redesplegó
  5. Desplegó cAdvisor con alerta al 70 % de memoria

Fue pesado, pero desde entonces no se repitió.

[Imagen: diagrama de flujo de diagnóstico de memoria]
Prompt: flowchart showing memory leak diagnosis process, step by step from detection to resolution, arrows connecting boxes, clean infographic style, blue and green colors, high quality

Mejores prácticas y trampas a evitar

Resumen de cómo usar esto bien en producción.

Lista obligatoria en producción

Todo contenedor debe tener límite de memoria
No confíes en la suerte. Aunque sea un Nginx estático, ponle 512MB. Mejor conservador que lamentar después.

En desarrollo, simula los límites de producción
No corras sin límites en local y descubras en producción que falta memoria. En dev, usa ~80 % de los valores de prod y detecta problemas antes.

Revisa el uso con regularidad
Una vez al mes mira docker stats; las cargas cambian — sube o baja recursos según toque.

No desactives el OOM Killer
Salvo que estés 100 % seguro de que la app controla su memoria, no uses --oom-kill-disable. Es un parámetro suicida.

No uses swap ilimitado
--memory-swap -1 tienta, pero es una mina. El swap masivo mata el disco; a veces es mejor que el OOM Killer actúe.

No pongas límites demasiado bajos
Por debajo de lo que la app necesita tendrás OOM constantes y peor disponibilidad. Haz pruebas de carga y luego fija el límite.

La forma correcta en Docker Compose

services:
  web:
    image: nginx:latest
    deploy:
      resources:
        limits:
          cpus: '1.5'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M
    restart: on-failure:3

Aquí deploy (sintaxis Compose v3). limits es duro; reservations es blando. En reposo 256MB puede bastar; en picos hasta 512MB.

Gestionar muchos contenedores a la vez

Para un grupo de microservicios con un pool común de recursos, cgroup-parent:

# Crear un cgroup padre con límite total
docker run --cgroup-parent=/my-services -m 2g service-a
docker run --cgroup-parent=/my-services -m 2g service-b
# Ambos comparten el cgroup padre; la memoria total no supera el límite del padre

Útil cuando varios contenedores forman una unidad de negocio y quieres controlar el conjunto.

Fórmulas orientativas de límites

Tipo de aplicaciónMemoria sugeridaCPU sugerida
Nginx estático256-512MB0,5-1 núcleo
API Node.js512MB-1GB1-2 núcleos
Microservicio Java1-2GB2-4 núcleos
Base de datos (MySQL/PostgreSQL)2-4GB2-4 núcleos
Cola de mensajes (RabbitMQ/Kafka)1-2GB1-2 núcleos

Son estimaciones conservadoras; el tráfico real manda. En pruebas de carga observa el pico y multiplica por ~1,5 para el límite.

Comparación con la gestión de recursos en Kubernetes

Si has usado Kubernetes, requests y limits se parecen a Docker:

  • requests: parecido a --memory-reservation
  • limits: parecido a --memory

K8s centraliza la configuración en YAML; Docker es más flexible (docker update al vuelo).

Último consejo

Los límites no son «configurar una vez y olvidar». La app evoluciona, el tráfico crece y los datos de monitorización te dirán si ampliar recursos u optimizar código. Revisa de vez en cuando; no dejes la configuración como adorno.

[Imagen: tabla comparativa de buenas prácticas de límites de recursos]
Prompt: comparison table showing Docker resource limits best practices, checkmarks and crosses, clean infographic style, professional layout, high quality

Resumen

Volvamos al inicio: despertar a las 3 de la madrugada porque el servidor cayó por un contenedor. Esa lección dolorosa me enseñó que la «libertad» por defecto de Docker es una trampa. Sin límites explícitos, le entregas al contenedor el poder de vida o muerte sobre el host.

Mirando atrás, la defensa es clara:

Primera línea: límites de recursos (prevención)
Pon --memory y --cpus en cada contenedor, como una rienda al caballo. Si se descontrola, cae solo sin arrastrar al servidor. Es el paso más básico y el más importante.

Segunda línea: monitorización y alertas (detección)
docker stats para el presente; cAdvisor + Prometheus + Grafana para tendencias e histórico. Alerta al 80 % de memoria y puedes ver la señal 48 horas antes del desastre.

Tercera línea: flujo de diagnóstico (respuesta)
Si pasa algo: detectar → analizar → contener → arreglar de raíz. Sin pánico; los comandos están en el artículo.

Desde cgroups hasta el detalle de --memory-swap, desde Compose hasta la comparación con Kubernetes, aquí está lo esencial de los límites en Docker. Lo que falta es actuar.

Haz estas tres cosas ahora:

  1. Revisa producción con este comando y mira qué contenedores no tienen límite:
docker ps --format "{{.Names}}" | xargs docker inspect \
  --format='{{.Name}}: Memory={{.HostConfig.Memory}} CPU={{.HostConfig.NanoCpus}}'

Memory=0 significa bomba de relojería.

  1. Despliega monitorización: copia el docker-compose.yml del artículo; en 30 minutos lo tienes.

  2. Pon un recordatorio mensual: 1 hora con docker stats para revisar si el uso es razonable.

No quiero que aprendas como yo, con una alarma a las 3 de la madrugada. Cuanto antes configures límites, menos problemas. No esperes al incidente.

Flujo completo de configuración de límites de recursos en Docker

Evita que una fuga de memoria en un contenedor tumbe el servidor: desde cgroups hasta --memory y --cpus en la práctica, y luego el plan de monitorización

⏱️ Estimated time: 1 hr

  1. 1

    Step 1: Entender la gravedad del problema y los cgroups

    Gravedad del problema:
    • Una fuga de memoria en un contenedor pasó de 500MB a 16GB
    • Agotó toda la RAM del servidor; ni SSH respondía
    • Otros contenedores en ejecución también cayeron; el entorno de producción se vino abajo

    Principio de cgroups:
    • Los cgroups del kernel de Linux controlan el uso de recursos de los contenedores
    • Docker limita CPU, memoria, E/S, etc. mediante cgroups
    • Evita que un solo contenedor tumbe todo el servidor

    En 2024, Docker 27.0.3 tuvo un bug grave de fuga de memoria que acabó con 68 contenedores de un solo golpe por el OOM Killer del kernel de Linux.
  2. 2

    Step 2: Configurar límites de recursos: memoria y CPU

    Límite de memoria:
    • Usa --memory: docker run --memory=512m container-name
    • Configura en docker-compose: deploy.resources.limits.memory: 512m
    • Define límite de swap: --memory-swap

    Límite de CPU:
    • Usa --cpus: docker run --cpus=1.0 container-name
    • Usa --cpu-shares para el peso de CPU
    • En docker-compose: deploy.resources.limits.cpus: '1.0'

    Verificación:
    • Usa docker stats para ver el uso de recursos
    • Confirma que los límites están activos
  3. 3

    Step 3: Desplegar monitorización y buenas prácticas

    Plan de monitorización:
    • docker stats en tiempo real: docker stats container-name
    • cAdvisor ofrece una interfaz visual:
    docker run -d -p 8080:8080 --name=cadvisor google/cadvisor
    • Prometheus+Grafana para alertas de nivel producción:
    configura Prometheus para scrapear métricas de cAdvisor y visualízalas en Grafana

    Buenas prácticas:
    • En producción, los límites de recursos son obligatorios
    • Define valores razonables (memoria, CPU)
    • Configura alertas de monitorización
    • Revisa periódicamente el uso de recursos (dedica 1 hora al mes a docker stats)
    • Actúa en cuanto detectes anomalías

FAQ

¿Por qué hay que configurar límites de recursos en Docker?
Gravedad del problema:
• Una fuga de memoria en un contenedor pasó de 500MB a 16GB
• Agotó toda la RAM del servidor; ni SSH respondía
• Otros contenedores en ejecución también cayeron; el entorno de producción se vino abajo

En 2024, Docker 27.0.3 tuvo un bug grave de fuga de memoria que acabó con 68 contenedores de un solo golpe por el OOM Killer del kernel de Linux.

Los límites evitan que un solo contenedor tumbe el servidor: cuando aparece una fuga de memoria, el contenedor cae solo en lugar de arrastrar a todo el host.
¿Cómo configurar límites de recursos en contenedores Docker?
Límite de memoria:
• Usa --memory: docker run --memory=512m container-name
• Configura en docker-compose: deploy.resources.limits.memory: 512m
• Define límite de swap: --memory-swap

Límite de CPU:
• Usa --cpus: docker run --cpus=1.0 container-name
• Usa --cpu-shares para el peso de CPU
• En docker-compose: deploy.resources.limits.cpus: '1.0'

Verificación:
• Usa docker stats para ver el uso de recursos
• Confirma que los límites están activos
¿Cómo monitorizar el uso de recursos de contenedores Docker?
Plan de monitorización:

docker stats en tiempo real:
• docker stats container-name

cAdvisor ofrece una interfaz visual:
• docker run -d -p 8080:8080 --name=cadvisor google/cadvisor

Prometheus+Grafana para alertas de nivel producción:
• Configura Prometheus para scrapear métricas de cAdvisor
• Visualiza en Grafana

Despliegue: copia el docker-compose.yml del artículo y ejecútalo; en 30 minutos lo tienes listo.

Revisión periódica: dedica 1 hora al mes a docker stats y comprueba si el uso de recursos es razonable.
¿Cuáles son las mejores prácticas para límites de recursos en Docker?
Buenas prácticas:
• En producción, los límites de recursos son obligatorios
• Define valores razonables (memoria, CPU)
• Configura alertas de monitorización
• Revisa periódicamente el uso de recursos (dedica 1 hora al mes a docker stats)
• Actúa en cuanto detectes anomalías

Cuanto antes configures los límites, menos dolores de cabeza. Pon un recordatorio en el calendario: 1 hora al mes con docker stats para revisar si el uso es razonable.

17 min de lectura · Publicado el: 18 dic 2025 · Actualizado el: 21 ago 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog