Docker: cómo los contenedores comparten el kernel de Linux
@programacionLo esencial
- Docker usa namespaces del kernel de Linux para aislar procesos, red y sistema de archivos sin virtualizar hardware.
- cgroups limita CPU y memoria por contenedor: docker run --memory=512m --cpus=1 aplica el límite al instante.
- Las imágenes se arman en capas con OverlayFS: cada instrucción del Dockerfile crea una capa reutilizable en caché.
- Un contenedor comparte el kernel del host, a diferencia de una máquina virtual que corre su propio kernel.
- docker build -t inventory-api:1.0 . genera una imagen; docker run -d -p 3000:3000 la ejecuta como contenedor.
- docker stats muestra en vivo el uso de CPU y memoria de cada contenedor corriendo en el host.
- En macOS y Windows, Docker Desktop corre una máquina virtual Linux porque namespaces y cgroups son exclusivos de Linux.
Cuando corrés docker run nginx, no arranca una computadora virtual. Linux crea un proceso normal, pero encerrado con namespaces y limitado con cgroups, dentro de una imagen armada en capas.
¿Qué aísla realmente un contenedor?
Un contenedor no es una máquina virtual liviana. Es un proceso normal del sistema operativo, encerrado con namespaces del kernel de Linux. Cada namespace oculta una vista distinta del sistema: el namespace PID hace que el proceso solo vea sus propios procesos, empezando en el PID 1. El namespace net le da su propia interfaz de red y el namespace mnt su propio árbol de archivos.
Podés inspeccionar los namespaces de un proceso con lsns -p <PID> en cualquier host Linux. El manual oficial de namespaces(7) documenta los ocho tipos que existen: PID, net, mnt, UTS, IPC, user, cgroup y time. Docker activa la mayoría de ellos en cada docker run.
cgroups: los límites de recursos
Los namespaces aíslan qué ve un proceso, pero no limitan cuánto puede consumir. Esa parte la resuelven los cgroups (control groups), otra pieza del kernel de Linux. Docker crea un cgroup por contenedor y le asigna cuotas de CPU, memoria y E/S de disco.
Para limitar un contenedor a 512 MB de memoria y un núcleo de CPU:
docker run -d --memory=512m --cpus=1 --name inventory-api inventory-api:1.0El límite se aplica de inmediato: podés confirmarlo con docker stats inventory-api, que lee los contadores del cgroup en tiempo real. La especificación completa vive en la documentación del kernel sobre cgroup v2.
Imágenes en capas: cómo arma Docker el sistema de archivos
Cada instrucción de un Dockerfile genera una capa de solo lectura. Docker las apila con un sistema de archivos llamado OverlayFS, y agrega una capa de escritura arriba para los cambios del contenedor en ejecución.
Si dos imágenes comparten las mismas capas base, Docker las reutiliza en disco en vez de duplicarlas. Podés ver el driver activo con docker info | grep 'Storage Driver' y el historial de capas de una imagen con docker history inventory-api:1.0.
Un Dockerfile real, paso a paso
Así se ve el Dockerfile de una API en Node.js que expone el puerto 3000:
FROM node:20-slim
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]La instrucción FROM node:20-slim fija la imagen base, que ya trae el runtime de Node. COPY package*.json ./ y RUN npm ci --omit=dev van antes de copiar el resto del código a propósito: si el package.json no cambió, Docker reutiliza la capa de dependencias desde caché en el próximo build.
De la imagen al contenedor corriendo
docker build -t inventory-api:1.0 .
docker run -d -p 3000:3000 --name inventory-api inventory-api:1.0
docker psdocker build lee el Dockerfile y arma la imagen inventory-api:1.0. docker run la instancia como contenedor, mapea el puerto 3000 del host al puerto 3000 del contenedor y lo corre en segundo plano con -d. docker ps confirma que está corriendo y muestra su ID.
Múltiples contenedores con Docker Compose
Cuando la app necesita más de un contenedor, por ejemplo la API y una base de datos, Docker Compose define ambos servicios en un solo archivo:
services:
api:
build: .
ports:
- "3000:3000"
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: inventory
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:docker compose up levanta los dos contenedores en la misma red virtual, donde el servicio api se conecta a la base con el hostname db. El volumen db-data persiste los datos de Postgres aunque el contenedor se reinicie.
Cuándo Docker no es la respuesta
Compartir el kernel del host explica por qué los contenedores arrancan en milisegundos, pero también marca su límite de seguridad. Una fuga de un contenedor compromete el kernel entero, algo que una máquina virtual con su propio kernel aísla mejor. Para cargas multiusuario no confiables conviene sumar una capa extra, como una VM dedicada.
En macOS y Windows, Docker Desktop no usa namespaces nativos porque esas features son exclusivas de Linux. Corre una máquina virtual liviana por debajo, lo que agrega una capa de traducción y hace que el rendimiento de disco, en particular, sea notablemente peor que en Linux nativo.
Conclusión
Docker no inventó los namespaces ni los cgroups: los tomó del kernel de Linux, que los tiene desde hace más de una década, y les puso una interfaz simple encima. Entender esa base explica por qué un contenedor arranca en milisegundos y por qué, al mismo tiempo, no reemplaza a una máquina virtual cuando el aislamiento tiene que ser total.