El Modelo de Contenedores para Node.js
Un contenedor no es una pequeña máquina virtual, y tratarlo como tal es donde comienza la mayor parte de la confusión sobre Docker.
Busca en todas las páginas de la documentación
Un contenedor no es una pequeña máquina virtual, y tratarlo como tal es donde comienza la mayor parte de la confusión sobre Docker.
Es un proceso Linux, aislado por características del kernel que ya existían antes de que Docker les diera una interfaz amigable, empaquetado a partir de una instantánea del sistema de archivos que nunca cambia una vez construida.
Para un servicio Node.js, el problema del empaquetado es específico: npm run start accede a bibliotecas del sistema, rutas del sistema de archivos y el árbol exacto de node_modules que tu archivo lockfile resolvió, y un contenedor es el mecanismo en el que la industria convergió para hacer que ese acceso sea reproducible dondequiera que se ejecute el servicio.
Conceptos básicos de Docker muestra los comandos diarios y los patrones de Dockerfile para una API de Node; esta página es el modelo subyacente a ellos: qué es realmente un contenedor y por qué existen las prácticas en el resto de esta sección.
Antes de los contenedores, "funciona en mi máquina" significaba algo más limitado de lo que los equipos querían.
Un servicio Node asume un tiempo de ejecución específico, bibliotecas del sistema específicas y un árbol de dependencias específico, y cualquier desajuste entre donde se escribió el código y donde se ejecutó producía errores que no tenían nada que ver con el código en sí.
Las máquinas virtuales resolvieron esto virtualizando una computadora completa (CPU, disco, un sistema operativo invitado completo), lo que garantiza el aislamiento pero cuesta minutos para arrancar y gigabytes por instancia.
Los contenedores resuelven el mismo problema de manera mucho más económica al aislar a nivel de proceso en lugar de a nivel de hardware, utilizando características del kernel (espacios de nombres y grupos de control) que habían existido en Linux durante años antes de que Docker las empaquetara en un flujo de trabajo en 2013.
Una imagen es el plano: una instantánea del sistema de archivos de solo lectura y en capas que nunca cambia una vez construida, distribuida a través de un registro de la misma manera que los paquetes npm se distribuyen a través del registro npm.
Un contenedor es una instancia viva de esa imagen: las mismas capas de solo lectura, más una capa delgada de escritura encima, más un conjunto de reglas de aislamiento del kernel que hacen que el proceso interno crea que tiene la máquina para sí mismo.
Una forma útil de visualizar una imagen es una pila de superposiciones transparentes, cada una registrando solo lo que cambió de la que está debajo: una capa base del SO, luego una capa de dependencias, luego una capa de código de aplicación, apiladas y vistas juntas como un solo sistema de archivos.
Ejecutar un contenedor agrega una superposición más encima, que se puede escribir, y que desaparece en el momento en que se elimina el contenedor, que es exactamente la razón por la que los contenedores están destinados a ser desechables y las imágenes no.
Dos primitivas del kernel de Linux realizan esencialmente todo el trabajo de aislamiento.
Los espacios de nombres le dan a un proceso su propia vista de los recursos compartidos en lugar de la real: un espacio de nombres PID lo convierte en el único proceso que puede ver (y se convierte en PID 1 dentro de su propio árbol), un espacio de nombres de red le da su propio bucle de retorno e interfaces, un espacio de nombres de montaje le da su propia vista del sistema de archivos.
Los cgroups (grupos de control) hacen el trabajo opuesto: no ocultan recursos, sino que los limitan, imponiendo una parte de CPU o un límite de memoria para que un contenedor ruidoso no pueda agotar el host o sus vecinos.
Juntos, un proceso aislado por espacio de nombres y limitado por cgroup es un contenedor; no hay una "magia de tiempo de ejecución de contenedor" separada más allá de orquestar estas dos primitivas y un sistema de archivos.
La estratificación importa operativamente, no solo conceptualmente: cada instrucción de Dockerfile que cambia el sistema de archivos produce una nueva capa con dirección de contenido, y Docker almacena en caché y reutiliza las capas que no han cambiado.
Esa es la verdadera razón por la que COPY package*.json ./ y RUN npm ci vienen antes de COPY src ./src en un Dockerfile bien escrito: no es una superstición, es asegurarse de que la capa lenta de instalación de dependencias permanezca en caché en las compilaciones donde solo cambió el código de la aplicación.
┌─────────────────────────────┐
│ capa de escritura (contenedor) │ descartada cuando se elimina el contenedor
├─────────────────────────────┤
│ capa: código fuente de la aplicación │ de `COPY src ./src`
├─────────────────────────────┤
│ capa: dependencias de producción │ de `RUN npm ci --omit=dev`
├─────────────────────────────┤
│ capa: imagen base │ ej. node:24-bookworm-slim
└─────────────────────────────┘
todas las capas comparten un kernel de Linux con el host
Cuando un contenedor se inicia, el kernel no arranca nada; aplica espacios de nombres y límites de cgroup a un proceso y le entrega la vista fusionada de esas capas como su sistema de archivos.
Esa es también la razón por la que los contenedores se inician en milisegundos donde las MV tardan minutos: no hay un sistema operativo que arrancar, solo un proceso que aislar.
Para Node específicamente, la convención de un proceso por contenedor se empareja directamente con la regla PID 1 del espacio de nombres PID: tu proceso Node se vuelve responsable del comportamiento que un sistema init real manejaría de otra manera, incluida la recolección correcta de señales al apagarse, la razón por la que el manejo elegante de SIGTERM importa tanto una vez que un servicio está en contenedores.
Debido a que los contenedores comparten el kernel del host, no son un límite de seguridad estricto como lo es una MV; una vulnerabilidad a nivel de kernel puede, en principio, cruzar un límite de contenedor que un hipervisor habría detenido.
Ese único hecho motiva la mayoría de las prácticas de endurecimiento en esta sección: ejecutar como un usuario no root limita lo que un proceso comprometido puede hacer al host incluso si escapa de las restricciones a nivel de aplicación, y una imagen base mínima (distroless o Alpine) reduce la cantidad de código con la que un atacante tiene que trabajar en primer lugar.
También vale la pena separar "Docker" de "contenedores" como tecnología: Docker popularizó el flujo de trabajo orientado al desarrollador, pero el trabajo de tiempo de ejecución real hoy en día lo realizan típicamente herramientas de nivel inferior (containerd, runc) que implementan el estándar OCI (Open Container Initiative), razón por la cual Kubernetes ya no necesita un demonio Docker para ejecutar contenedores construidos por Docker.
Las compilaciones multi-etapa existen porque el enfoque ingenuo (instalar todas las dependencias, incluidas las herramientas de desarrollo, en una sola imagen) produce artefactos inflados, lentos de extraer y con una superficie de ataque más grande; Compilaciones Multi-Etapa cubre el patrón que mantiene las herramientas solo de compilación fuera de la imagen de tiempo de ejecución por completo.
Un solo contenedor tampoco es, por sí mismo, una historia de despliegue en producción: no tiene política de reinicio, ni programación entre máquinas, ni actualizaciones continuas.
Ese es el límite donde termina el modelo de esta página y comienza la orquestación: un programador que decide dónde se ejecutan los contenedores y los mantiene en funcionamiento es una capa diferente de preocupación, cubierta en El Modelo de Orquestación de Contenedores.
| Enfoque de Aislamiento | Fortaleza | Debilidad | Mejor Ajuste |
|---|---|---|---|
| Máquina virtual | Aislamiento fuerte - kernel separado por invitado | Arranque lento, sobrecarga pesada por instancia | Alojamiento multi-inquilino, cargas de trabajo no confiables |
| Contenedor (espacios de nombres + cgroups) | Inicio rápido, pequeña huella, imagen portátil | Comparte el kernel del host - aislamiento más débil que una MV | Empaquetado y despliegue de servicios de aplicación |
| Proceso sin contenedor en un host | Sin sobrecarga de aislamiento | Sin aislamiento de dependencias o recursos entre aplicaciones | Hosts de un solo inquilino, estrictamente controlados |
Una imagen es una instantánea inmutable y en capas del sistema de archivos, un plano que nunca cambia una vez construido. Un contenedor es una instancia en ejecución de esa imagen: las mismas capas de solo lectura más una capa de escritura y un conjunto de reglas de aislamiento del kernel aplicadas a un proceso en vivo.
Una MV tiene que arrancar un sistema operativo invitado completo antes de que algo pueda ejecutarse dentro de ella. Un contenedor se salta eso por completo: el kernel simplemente aplica espacios de nombres y límites de cgroup a un proceso y le entrega una vista fusionada del sistema de archivos, por lo que "iniciar" un contenedor es más parecido a iniciar un proceso que a arrancar una máquina.
Los espacios de nombres le dan a un proceso su propia vista privada de los recursos compartidos (su propia lista de procesos, sus propias interfaces de red, sus propios montajes del sistema de archivos) en lugar de la vista real de todo el host. Los cgroups limitan lo que ese proceso puede consumir, como la parte de CPU o el límite de memoria, para que un contenedor no pueda agotar a sus vecinos.
No, Docker popularizó el flujo de trabajo, pero el estándar OCI (Open Container Initiative) significa que otras herramientas (containerd, runc, Podman) pueden construir y ejecutar las mismas imágenes. Kubernetes, por ejemplo, ejecuta contenedores sin un demonio Docker en absoluto.
Docker almacena en caché cada capa y la reutiliza en las compilaciones si nada de lo que la produjo ha cambiado. Poner los pasos lentos y que rara vez cambian (instalación de dependencias) antes de los rápidos y que cambian con frecuencia (copia del código fuente de la aplicación) significa que la mayoría de las compilaciones solo vuelven a ejecutar las capas finales baratas.
Más débil que el de una máquina virtual, porque los contenedores comparten el kernel del host en lugar de ejecutar el suyo propio. Esa es exactamente la razón por la que los usuarios no root, los sistemas de archivos de solo lectura y las imágenes base mínimas importan más para los contenedores de lo que lo harían para un invitado completamente virtualizado.
Vive en la capa superior de escritura del contenedor, que se descarta en el momento en que se elimina el contenedor. Todo lo que necesite persistir más allá de la vida útil de un solo contenedor debe ir a un volumen externo o a un servicio de respaldo completamente fuera del contenedor.
Se empareja directamente con el espacio de nombres PID, donde el proceso principal del contenedor se convierte en PID 1 dentro de su propio árbol de procesos aislado. Mantenerlo en un proceso claro (tu servidor Node) también mantiene las políticas de reinicio, las comprobaciones de estado y los flujos de registro simples y sin ambigüedades.
Esta página cubre lo que es un solo contenedor. La orquestación (decidir dónde se ejecutan los contenedores, reiniciar los fallidos, escalar el número de réplicas) es una capa separada construida sobre eso, cubierta en El Modelo de Orquestación de Contenedores.
Cada paquete y binario en una imagen base es algo que un atacante podría usar potencialmente si obtiene la ejecución de código dentro del contenedor. Una imagen base más pequeña reduce esa superficie de ataque y, por lo general, produce un artefacto más pequeño y rápido de extraer como beneficio secundario.
No, una vez que un contenedor alcanza su límite de memoria de cgroup, el kernel interviene, típicamente matando el proceso (una eliminación por OOM) en lugar de permitir que exceda el límite. Por eso, establecer límites de memoria realistas, informados por el uso real, es importante para la estabilidad.
Una capa de imagen individual se construye para una arquitectura de CPU específica (por ejemplo, x86-64 o arm64), aunque los registros suelen alojar manifiestos de imágenes multi-arquitectura que permiten que la misma etiqueta se resuelva automáticamente a la variante correcta para la máquina que la extrae.
Versiones de la pila: Esta página es conceptual y no está ligada a una versión específica de la pila.
Revisado por Chris St. John·Última actualización: 19 jul 2026