--- title: "Cómo limitar CPU y memoria de contenedores Docker" description: "Limita la CPU, la memoria, el swap y los procesos de tus contenedores con docker run y Docker Compose, y comprueba en cgroups v2 que los límites se aplican." category: "Docker Avanzado" language: es source: https://cubepath.com/es/docs/docker-avanzado/limites-de-recursos-de-docker-cpu-y-memoria updated: 2026-09-25 --- # Cómo limitar CPU y memoria de contenedores Docker Por defecto, un contenedor Docker puede usar toda la CPU y toda la memoria del servidor. Si una aplicación tiene una fuga de memoria o entra en un bucle, puede dejar sin recursos al resto de contenedores e incluso al sistema. En este tutorial aprenderás a limitar la memoria, el swap, la CPU y el número de procesos de un contenedor en Ubuntu 24.04, tanto con `docker run` como con Docker Compose, y a comprobar en cgroups v2 que el kernel aplica esos límites. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 2 vCPU para ver bien el efecto de los límites de CPU. - Un usuario no root con privilegios `sudo` que pertenezca al grupo `docker`. - Docker Engine y el plugin Docker Compose instalados desde el repositorio oficial de Docker. ## Cómo aplica Docker los límites Docker no controla los recursos por sí mismo: crea un grupo de control (cgroup) del kernel para cada contenedor y escribe en él los límites. Ubuntu 24.04 usa cgroups v2, y dentro de un contenedor los ficheros relevantes están en `/sys/fs/cgroup/`: | Opción de Docker | Fichero de cgroups v2 | Efecto | |---|---|---| | `--memory` | `memory.max` | Límite duro de RAM; si se supera, el kernel mata procesos del contenedor | | `--memory-reservation` | `memory.low` | Mínimo que el kernel intenta respetar cuando falta memoria | | `--memory-swap` | `memory.swap.max` | Swap adicional permitido | | `--cpus` | `cpu.max` | Tope de CPU (por ejemplo, 1,5 núcleos) | | `--cpu-shares` | `cpu.weight` | Peso relativo cuando varios contenedores compiten por la CPU | | `--cpuset-cpus` | `cpuset.cpus` | Núcleos concretos en los que puede ejecutarse | | `--pids-limit` | `pids.max` | Número máximo de procesos e hilos | Comprueba que tu sistema usa cgroups v2: ```bash docker info --format '{{.CgroupVersion}} {{.CgroupDriver}}' ``` ```output 2 systemd ``` ## Paso 1: Limitar la memoria La opción `--memory` (o `-m`) fija el máximo de RAM. Acepta los sufijos `k`, `m` y `g`. Arranca un Nginx con 256 MB: ```bash docker run -d --name web-limitado --memory 256m nginx:alpine ``` Comprueba el límite de tres formas distintas. Primero, en la configuración del contenedor (el valor está en bytes): ```bash docker inspect web-limitado --format '{{.HostConfig.Memory}}' ``` ```output 268435456 ``` Después, en el cgroup que ve el propio contenedor: ```bash docker exec web-limitado cat /sys/fs/cgroup/memory.max ``` ```output 268435456 ``` Por último, en `docker stats`, que muestra el consumo actual frente al límite: ```bash docker stats --no-stream web-limitado ``` ```output CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS 4b1e9c2a7d3f web-limitado 0.00% 3.1MiB / 256MiB 1.21% 1.2kB / 0B 0B / 8kB 3 ``` ### Qué ocurre al superar el límite Cuando el contenedor intenta usar más memoria de la permitida, el OOM killer del kernel mata el proceso. Puedes provocarlo con `tail /dev/zero`, que acumula datos en memoria sin parar. Limita también el swap al mismo valor para que no pueda desbordarse al disco: ```bash docker run --name oom-test --memory 64m --memory-swap 64m alpine tail /dev/zero echo "Código de salida: $?" ``` ```output Código de salida: 137 ``` El código 137 significa que el proceso recibió `SIGKILL`. Confirma que la causa fue la memoria: ```bash docker inspect oom-test --format 'OOMKilled={{.State.OOMKilled}}' ``` ```output OOMKilled=true ``` Borra el contenedor de prueba: ```bash docker rm oom-test ``` Si un servicio real muestra `OOMKilled=true`, no basta con subir el límite a ciegas: revisa con `docker stats` cuánto consume en carga normal y fija el límite con margen por encima. ## Paso 2: Controlar el swap y la reserva de memoria La opción `--memory-swap` indica el total de **memoria más swap**, no el swap por separado. Las combinaciones posibles son: | `--memory` | `--memory-swap` | Resultado | |---|---|---| | `512m` | sin indicar | 512 MB de RAM y hasta 512 MB de swap | | `512m` | `512m` | 512 MB de RAM y sin swap | | `512m` | `1g` | 512 MB de RAM y hasta 512 MB de swap | | `512m` | `-1` | 512 MB de RAM y swap ilimitado | Para servicios sensibles a la latencia, como bases de datos, lo habitual es desactivar el swap del contenedor igualando ambos valores. La reserva (`--memory-reservation`) es un límite blando: el contenedor puede superarla, pero cuando el servidor se queda sin memoria el kernel intenta no recortarle por debajo de ese valor. Debe ser menor que `--memory`: ```bash docker run -d --name db-test \ --memory 1g --memory-swap 1g --memory-reservation 512m \ -e POSTGRES_PASSWORD=tu_contraseña_segura \ postgres:17 ``` Comprueba los tres valores en el cgroup: ```bash docker exec db-test sh -c 'cat /sys/fs/cgroup/memory.max /sys/fs/cgroup/memory.low /sys/fs/cgroup/memory.swap.max' ``` ```output 1073741824 536870912 0 ``` Borra el contenedor de prueba: ```bash docker rm -f db-test ``` ## Paso 3: Limitar la CPU ### Tope de CPU con --cpus `--cpus` es la forma más sencilla de limitar la CPU: `--cpus 0.5` permite usar como máximo medio núcleo, y `--cpus 1.5`, núcleo y medio, repartido entre los núcleos disponibles. Arranca un contenedor que consume CPU sin parar, limitado a medio núcleo: ```bash docker run -d --name cpu-test --cpus 0.5 alpine sh -c 'while :; do :; done' ``` Mide su consumo: ```bash docker stats --no-stream cpu-test ``` ```output CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS 9a3d7e1f5b2c cpu-test 50.12% 412KiB / 3.8GiB 0.01% 0B / 0B 0B / 0B 1 ``` En `docker stats`, el 100 % equivale a un núcleo completo, así que el contenedor se queda en torno al 50 %. En el cgroup, el límite aparece como cuota y periodo en microsegundos (50 ms de cada 100 ms): ```bash docker exec cpu-test cat /sys/fs/cgroup/cpu.max ``` ```output 50000 100000 ``` ### Cambiar límites sin recrear el contenedor `docker update` modifica los límites de un contenedor en marcha. Sube el límite a un núcleo y comprueba el cambio: ```bash docker update --cpus 1 cpu-test docker exec cpu-test cat /sys/fs/cgroup/cpu.max ``` ```output cpu-test 100000 100000 ``` Los cambios hechos con `docker update` se pierden si el contenedor se recrea (por ejemplo, con `docker compose up -d`), así que trasládalos después a tu configuración. ### Fijar núcleos con --cpuset-cpus `--cpuset-cpus` restringe el contenedor a núcleos concretos, numerados desde 0 (acepta listas como `0,2` y rangos como `0-1`). Es útil para aislar una carga pesada en un núcleo y dejar el resto libre. `nproc` dentro del contenedor muestra cuántos núcleos puede usar: ```bash docker run --rm --cpuset-cpus 0 alpine nproc ``` ```output 1 ``` ### Prioridad relativa con --cpu-shares `--cpu-shares` no es un límite: solo reparte la CPU cuando hay competencia. El valor por defecto es 1024; un contenedor con 2048 recibirá el doble de tiempo de CPU que uno con 1024 cuando ambos quieran la CPU completa. Si el servidor está desocupado, cualquier contenedor puede usar toda la CPU. Detén el contenedor de prueba: ```bash docker rm -f cpu-test ``` ## Paso 4: Limitar el número de procesos Un fallo que crea procesos sin control (o una bomba fork) puede agotar la tabla de procesos del servidor aunque la CPU y la memoria estén limitadas. `--pids-limit` lo evita: ```bash docker run --rm --pids-limit 50 alpine cat /sys/fs/cgroup/pids.max ``` ```output 50 ``` Un valor entre 100 y 500 suele bastar para servicios web; ajústalo según los hilos que cree tu aplicación. ## Paso 5: Definir límites en Docker Compose En Docker Compose, los límites se declaran en `deploy.resources`, que Compose aplica también fuera de Swarm. Crea un proyecto de ejemplo: ```bash mkdir -p ~/limites && cd ~/limites nano compose.yaml ``` ```yaml services: web: image: nginx:alpine ports: - "8080:80" pids_limit: 200 deploy: resources: limits: cpus: "1.0" memory: 512M reservations: memory: 128M worker: image: alpine command: sleep infinity mem_limit: 256m memswap_limit: 256m cpus: 0.5 ``` El servicio `web` usa la sintaxis `deploy.resources` y el servicio `worker`, las claves equivalentes a nivel de servicio (`mem_limit`, `memswap_limit`, `cpus`). Ambas funcionan con `docker compose`; elige una y úsala de forma coherente en todo el proyecto. `memswap_limit` no tiene equivalente en `deploy.resources`, por lo que es la forma de controlar el swap en Compose. Arranca los servicios y comprueba los límites: ```bash docker compose up -d docker stats --no-stream ``` ```output CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS 1f2e3d4c5b6a limites-web-1 0.00% 3.4MiB / 512MiB 0.66% 1.1kB / 0B 0B / 8kB 3 6a5b4c3d2e1f limites-worker-1 0.00% 328KiB / 256MiB 0.12% 1.1kB / 0B 0B / 0B 1 ``` Verifica también los límites de CPU y de procesos del servicio `web`: ```bash docker compose exec web cat /sys/fs/cgroup/cpu.max /sys/fs/cgroup/pids.max ``` ```output 100000 100000 200 ``` Cuando termines, elimina el proyecto de prueba: ```bash docker compose down ``` ## Cómo elegir los valores - **Mide antes de limitar**: observa el consumo con `docker stats` durante un periodo de carga real y fija el límite de memoria entre un 25 % y un 50 % por encima del pico. - **Deja memoria para el sistema**: la suma de los límites de memoria de todos los contenedores no debería superar la RAM del servidor menos unos cientos de MB para el sistema operativo y Docker. - **Ajusta la aplicación al límite**: algunos entornos no leen el cgroup y se configuran según la RAM total del servidor. Por ejemplo, fija `shared_buffers` en PostgreSQL o `-Xmx` en Java por debajo del límite del contenedor. - **Limita todo lo que no controlas**: contenedores de terceros, tareas por lotes y procesos de compilación son los primeros candidatos. ## Solución de problemas - **`Minimum memory limit allowed is 6MB`**: el límite es demasiado bajo. Usa valores realistas; ningún servicio útil funciona con unos pocos MB. - **`Memory limit should be smaller than already set memoryswap limit`**: al subir `--memory` con `docker update`, sube también `--memory-swap` en el mismo comando, ya que la memoria nunca puede superar el total de memoria más swap. - **El contenedor se reinicia en bucle con código 137**: revisa `docker inspect nombre --format '{{.State.OOMKilled}}'`. Si es `true`, el límite de memoria es insuficiente para su carga; si es `false`, otro proceso o una sonda de salud lo está matando. - **`Range of CPUs is from 0.01 to N.00`**: has pedido más CPU de la que tiene el servidor. Comprueba los núcleos disponibles con `nproc`. ## Conclusión Has limitado la memoria, el swap, la CPU y los procesos de tus contenedores con `docker run`, `docker update` y Docker Compose, y has comprobado en cgroups v2 que el kernel aplica cada límite. Como siguientes pasos, añade límites a todos los servicios de tus ficheros de Compose, recoge las métricas de `docker stats` en un sistema de monitorización para ajustar los valores con datos reales y configura `healthcheck` para detectar contenedores que se quedan sin recursos.