Cómo elegir la mejor imagen de Docker para Node.js
30 de septiembre de 2022
0 minutos de lectura
Nota del editor:
Actualizado para incluir Chainguard Distroless Image para Node.js.
(31 de agosto de 2023) Se actualizaron la versión recomendada de Node.js, los ejemplos y los resultados del análisis de vulnerabilidades para reflejar las versiones LTS más recientes de Node.js.
Elegir una imagen de Docker para Node.js puede parecer algo menor, pero el tamaño de las imágenes y las posibles vulnerabilidades pueden tener un impacto significativo en tu pipeline de CI/CD y tu postura de seguridad. Entonces, ¿cómo eliges la mejor imagen de Docker para Node.js?
Es fácil pasar por alto los riesgos potenciales de usar FROM node:latest o simplemente FROM node (que es un alias de la anterior). Esto es aún más cierto si desconoces los riesgos generales de seguridad y el gran tamaño de los archivos que introducen en un pipeline de CI/CD.
A continuación, se muestra un ejemplo de Dockerfile para Node.js que suele usarse como referencia en tutoriales y publicaciones de blog sobre imágenes de Docker para Node.js; sin embargo, este Dockerfile tiene graves deficiencias y no se recomienda:
Anteriormente, describí y ofrecí una guía paso a paso sobre 10 prácticas recomendadas para contenerizar aplicaciones web de Node.js con Docker, que amplía y mejora el ejemplo para crear una imagen de Docker para Node.js lista para producción.
En esta publicación, usaremos el ejemplo hipotético anterior como contenido de un Dockerfile para encontrar una imagen de Docker ideal para Node.js.
Tus opciones de imágenes de Docker para Node.js
En realidad, hay bastantes opciones para crear tu imagen de Node.js. Van desde la imagen oficial de Docker para Node.js, mantenida por el equipo principal de Node.js, hasta las etiquetas específicas de imagen de Node.js que puedes elegir dentro de esa imagen base de Docker, e incluso otras opciones, como crear tu aplicación de Node.js sobre una imagen distroless de Google o Chainguard, o una imagen scratch básica proporcionada por el equipo de Docker.
De todas estas opciones, ¿qué imagen de Docker para Node.js es ideal para ti?
Veámoslas una por una para conocer mejor sus ventajas y posibles riesgos.
Nota del autor: A lo largo de este artículo, compararé con una versión de Node.js disponible en un momento específico, lanzada por última vez alrededor de abril de 2024: Node.js 22.1.0.
La imagen node predeterminada
Empecemos con la imagen node, que recibe mantenimiento. El equipo de Node.js Docker la mantiene oficialmente y contiene varias etiquetas de imagen base de Docker, que corresponden a distintas distribuciones subyacentes (Debian, Ubuntu o Alpine) y distintas versiones del entorno de ejecución de Node.js. También hay etiquetas para versiones específicas que permiten elegir arquitecturas de CPU como amd64 o arm64x8 (la nueva Apple M1).
Las etiquetas de imagen node más comunes para la distribución Debian, como bullseye o bookworm, se basan a su vez en buildpack-deps, que mantiene otro equipo.
¿Qué ocurre si creas tu imagen de Docker para Node.js a partir de esta imagen node predeterminada, con solo la dependencia npm fastify? Para simplificar, usaremos este ejemplo en lugar de implementar una aplicación completa.
Desde el mismo directorio, crea la imagen con docker build --no-cache -t mynode . — También descargué la imagen node:latest para mostrar la diferencia de tamaño. Esto es lo que obtienes:
No especificamos la versión del entorno de ejecución de Node.js, así que
nodees un alias denode:latest, que en este momento apunta a la versión 22.1.0 de Node.js.El tamaño de nuestra imagen de Docker es de 1.13 GB.
La imagen base
node:latestocupa 1.11 GB de ese tamaño.
¿Cuál es la huella de dependencias y vulnerabilidades de seguridad de esta imagen de Node.js más reciente? Podemos averiguarlo con un análisis de contenedores. Si quieres seguir los pasos y ejecutar los mismos análisis en tus compilaciones, necesitarás una cuenta gratuita de Snyk e instalar Snyk CLI. Para hacerlo, basta con ejecutar `npm install -g snyk` o seguir las instrucciones de nuestra documentación.
Ejecutaremos snyk container test mynode --file=Dockerfile --exclude-app-vulns, que revela lo siguiente:
Un total de 413 dependencias: cualquier biblioteca de código abierto detectada mediante el administrador de paquetes del sistema operativo, como
curl/libcurl4,git/git-manoimagemagick/imagemagick-6-common.Se encontraron un total de 179 problemas de seguridad en esas dependencias, como desbordamientos de búfer, errores de uso después de liberar memoria, escrituras fuera de los límites y otros.
Se encontraron vulnerabilidades en imágenes anteriores de Node.js, como Node.js 20.5.1, relacionadas con distintos problemas de seguridad, como repetición de DNS, contrabando de solicitudes HTTP y secuestro de configuración.
¿De verdad necesitas tener wget, git o curl disponibles en la imagen de Node.js para tu aplicación? En general, el panorama no es alentador: cientos de dependencias y herramientas en la imagen de Docker para Node.js, con cientos de vulnerabilidades asociadas, dejan mucho espacio para posibles ataques.
Opciones de Node.js en Docker Hub: node:buster vs. node:bullseye vs. node:bookworm
Si exploras las etiquetas disponibles en el repositorio de Node.js en Docker Hub, encontrarás varias opciones de etiquetas alternativas para imágenes de Node.js, como node:buster, node:bullseye y node:bookworm.
Estas tres etiquetas de imagen de Docker se basan en distintas versiones de la distribución Debian. La etiqueta de imagen buster corresponde a Debian 10, que llegó al fin de su ciclo de vida entre agosto de 2022 y 2024, por lo que no es una buena opción. La etiqueta de imagen bullseye corresponde a Debian 11, considerada la versión «old stable» actual de Debian, con una fecha estimada de fin de ciclo de vida en junio de 2026. Por último, bookworm es la versión «stable» actual y, al momento de escribir esto, todavía no se ha determinado cuándo llegará al fin de su ciclo de vida; aunque, según los ciclos históricos, probablemente será en algún momento de 2028.
Nota del autor: Por este motivo, te recomendamos encarecidamente cambiar todas las imágenes de Docker para Node.js, nuevas y existentes, con etiquetas node:buster por node:bullseye, node:bookworm, u otras alternativas adecuadas.
Creemos una nueva imagen de Docker para Node.js basada en:
Si creas esta etiqueta de imagen de Docker para Node.js y la comparas con los resultados anteriores, obtendrás exactamente el mismo tamaño, la misma cantidad de dependencias y las mismas vulnerabilidades detectadas. Esto se debe a que node, node:latest y node:bookworm apuntan a la misma etiqueta de imagen de Node.js.
Etiqueta de imagen de Node.js para imágenes más ligeras
El equipo oficial de Docker para Node.js también mantiene una etiqueta de imagen que incluye explícitamente solo las herramientas necesarias para un entorno funcional de Node.js.
Estas etiquetas de imagen de Node.js se identifican con la variante slim, como node:bookworm-slim, o con una versión específica de Node.js, como node:20-slim.
Creemos una imagen slim de Node.js basada en la versión estable actual de Debian, bookworm:
El tamaño de la imagen disminuyó drásticamente: pasó de casi un gigabyte a 231 MB. El análisis de su contenido también muestra una gran reducción de la huella general del software, con 89=8 dependencias y solo 37 vulnerabilidades.
node:bookworm-slim ya es un mejor punto de partida en cuanto al tamaño de la imagen de contenedor y la postura de seguridad.
Una imagen de Docker para Node.js con LTS
Hasta ahora, nuestras imágenes de Docker para Node.js se basaban en la versión actual de Node.js, que es Node.js 22. Sin embargo, según el calendario de lanzamientos de Node.js, esta versión no alcanzará el estado oficial Active LTS hasta octubre de 2024.
¿Qué tal si siempre usáramos versiones con soporte a largo plazo (LTS) en las imágenes de Docker para Node.js que creamos? Actualicemos la etiqueta de imagen y creemos una nueva imagen de Node.js:
La versión LTS más ligera de Node.js (20.13.1) incluye una cantidad similar de dependencias y vulnerabilidades de seguridad en la imagen, con un tamaño ligeramente menor, de 219 MB.
Así que, aunque quizás tengas requisitos específicos para elegir entre versiones LTS y Current del entorno de ejecución de Node.js, ninguna de ellas afecta de manera significativa la huella de software de la imagen de Node.js.
¿Es node:alpine una mejor opción para una imagen de Node.js?
El equipo de Docker para Node.js mantiene la etiqueta de imagen node:alpine y sus variantes, que combinan versiones específicas de las distribuciones Alpine Linux con las del entorno de ejecución de Node.js.
El proyecto Alpine Linux suele destacarse por el tamaño extraordinariamente pequeño de sus imágenes. Esto es útil porque implica una menor huella de software y, por ende, una superficie de vulnerabilidad más reducida.
Esto genera una imagen de Docker de 167 MB, 64 MB menos que las imágenes slim de Node.js. Además, en la etiqueta de imagen Alpine, al momento de escribir esto, se detectaron solo 17 dependencias del sistema operativo y una vulnerabilidad de seguridad. Esto podría indicar que la etiqueta de imagen alpine es una buena opción para reducir tanto el tamaño de la imagen como la cantidad de vulnerabilidades.
¿Es node:alpine la mejor opción para una imagen de Docker para Node.js?
La variante de imagen Alpine para Node.js puede ofrecer un tamaño general de imagen pequeño y una cantidad aún menor de vulnerabilidades. Sin embargo, es importante tener en cuenta que el proyecto Alpine usa musl como implementación de la biblioteca estándar de C, mientras que las etiquetas de imagen Debian para Node.js, como bullseye o slim, dependen de la implementación glibc. Estas diferencias pueden causar problemas de rendimiento, errores funcionales o posibles fallas de la aplicación debido a las diferencias entre las bibliotecas C subyacentes. Itamar Turner-Trauring también escribió sobre su experiencia con problemas inesperados durante la ejecución relacionados con las etiquetas de imagen Alpine para imágenes de Docker de Python.
Elegir una etiqueta de imagen alpine para Node.js significa que, en la práctica, eliges un entorno de ejecución de Node.js no oficial. El equipo de Docker para Node.js no ofrece soporte oficial para las compilaciones de imágenes de contenedor basadas en Alpine. Por eso, indica que las etiquetas de imagen basadas en Alpine son experimentales y pueden no ser consistentes, y las ofrece en las siguientes compilaciones no oficiales. Esta es una cita del repositorio de etiquetas de imagen Unofficial Builds:
Unofficial-builds intenta proporcionar binarios básicos de Node.js para algunas plataformas que Node.js no admite o admite solo parcialmente. Este proyecto no ofrece garantías y sus resultados no se prueban rigurosamente. Las compilaciones disponibles en nodejs.org cumplen estándares de calidad muy altos en cuanto a calidad del código, soporte en las plataformas pertinentes y tiempos y métodos de entrega. Las compilaciones disponibles en unofficial-builds se someten a pruebas mínimas o no se prueban; es posible que las plataformas no estén incluidas en la infraestructura oficial de pruebas de Node.js. Estas compilaciones se ofrecen para comodidad de la comunidad de usuarios, pero se espera que esas comunidades ayuden a mantenerlas.
Algunas observaciones importantes sobre la compatibilidad de la etiqueta de imagen alpine de Node.js:
Yarn no es compatible (incidencia n.º 1716).
Si necesitas
node-gyppara compilar de forma cruzada enlaces nativos de C, Python, que es una dependencia de ese proceso, no está disponible en la imagen Alpine y tendrás que resolverlo por tu cuenta (incidencia n.º 1706).
Imágenes de Docker Distroless para Node.js
Los últimos elementos de comparación de nuestro análisis son las imágenes Distroless. Hay dos opciones principales: las imágenes de contenedor originales Distroless de Google y las más recientes imágenes de contenedor distroless de Chainguard.
¿Qué es una imagen de Docker distroless?
Estas imágenes son aún más ligeras que la etiqueta de imagen slim de Node.js, porque solo incluyen la aplicación y sus dependencias de ejecución. Por eso, una imagen de Docker distroless no tiene administrador de paquetes, shell ni otras herramientas de uso general, lo que reduce su tamaño y su huella de vulnerabilidades.
Imagen Distroless de Google
El proyecto Distroless de Google mantiene una imagen de Docker distroless específica para el entorno de ejecución de Node.js, identificada con el espacio de nombres completo gcr.io/distroless/nodejs22-debian12 y disponible en el registro de contenedores de Google (la parte gcr.io).
Nota: También existen combinaciones anteriores de Debian y Node.js, pero asegúrate de elegir una versión que siga recibiendo mantenimiento. Para consultar las imágenes compatibles más recientes, revisa la tabla del repositorio de Distroless en GitHub.
Como las imágenes de contenedor Distroless no incluyen software, podemos usar un flujo de trabajo de Docker de varias etapas para instalar las dependencias del contenedor y copiarlas a las imágenes distroless:
Al compilar esta imagen de Docker distroless se obtiene un archivo de 177 MB, más pequeño que las variantes de etiquetas de imagen slim y alpine .
Si estás pensando en usar imágenes de Docker distroless, debes tener en cuenta algunos aspectos importantes:
Se basan en versiones estables actuales de Debian, por lo que están actualizadas y tienen una fecha de fin de vida muy lejana, lo cual es excelente.
Como se basan en Debian, dependen de la implementación de glibc y es menos probable que te sorprendan con problemas en producción.
Pronto descubrirás que el equipo de Distroless no mantiene versiones específicas del entorno de ejecución de Node.js. Esto significa que debes depender de la etiqueta de uso general
latest, que se actualiza con frecuencia, o instalar la imagen usando su hash SHA256 en un momento determinado.
Imagen Distroless de Chainguard
La otra opción para las imágenes distroless es Chainguard. Chainguard ofrece varias imágenes, incluidas algunas para versiones anteriores de Node y versiones compatibles con FIPS. En el nivel gratuito para desarrolladores hay dos imágenes disponibles: "latest", que ejecuta Node 22.1.0 al momento de escribir esto, y "latest-dev", que amplía la imagen más reciente con un administrador de paquetes y un shell para desarrollo y depuración.
Podemos usar un ejemplo muy similar al anterior de Google Distroless:
El resultado es una imagen de 142 MB, similar a la imagen de Google Distroless. No se reportan vulnerabilidades.
Vale la pena mencionar que todas las imágenes de Chainguard se crean sobre la distribución Linux Wolfi de Chainguard. Wolfi se compila con glibc, por lo que no hay problemas de compatibilidad con musl.
Comparación de las etiquetas de imagen de Docker para Node.js
La siguiente tabla resume nuestra comparación de distintas etiquetas de imagen de Docker para Node.js, actualizada por última vez el 15 de mayo de 2024:
Etiqueta de imagen | Versión del entorno de ejecución de Node.js | Dependencias del sistema operativo | Vulnerabilidades de seguridad del sistema operativo | Vulnerabilidades altas y críticas | Vulnerabilidades medias | Vulnerabilidades bajas | Vulnerabilidades del entorno de ejecución de Node.js | Tamaño de la imagen | Yarn disponible |
|---|---|---|---|---|---|---|---|---|---|
node:latest | 22.1.0 | 413 | 179 | 3 | 0 | 176 | 0 | 1135 MB | Sí |
node:bookworm | 22.1.0 | 413 | 179 | 3 | 0 | 176 | 0 | 1135 MB | Sí |
node:bookworm-slim | 22.1.0 | 88 | 37 | 2 | 0 | 35 | 0 | 233 MB | Sí |
node:lts-bookworm-slim | 20.13.1 | 88 | 37 | 2 | 0 | 35 | 0 | 219 MB | Sí |
node:alpine | 22.1.0 | 17 | 1 | 0 | 0 | 1 | 0 | 145 MB | Sí |
22.1.0 | 8 | 16 | 0 | 0 | 16 | 0 | 186 MB | No | |
cgr.dev/chainguard/node:latest | 22.1.0 | 25 | 0 | 0 | 0 | 0 | 0 | 134 MB | No |
cgr.dev/chainguard/node:latest-dev | 22.1.0 | 66 | 0 | 0 | 0 | 0 | 0 | 651 MB | Sí |
Revisemos los datos y aprendizajes de cada una de las distintas etiquetas de imagen de Node.js para decidir cuál es la más adecuada.
Paridad con el entorno de desarrollo
Si eliges qué etiqueta de imagen de Node.js usar en función de la coherencia con el entorno de desarrollo —es decir, si quieres optimizar para que los entornos de desarrollo y producción sean exactamente iguales—, quizá ya sea una batalla perdida. En la mayoría de los casos, cada uno de los tres principales sistemas operativos usa una implementación distinta de la biblioteca C. Linux depende de glibc, Alpine de musl y macOS tiene su propia implementación BSD libc.
Tamaño de la imagen de Docker
A veces, el tamaño importa. Sin embargo, el objetivo no es tener la imagen más pequeña, sino reducir al mínimo la huella total del software. En ese caso, las etiquetas de imagen slim no difieren mucho en tamaño de sus equivalentes alpine: todas promedian unos 211 MB por imagen de contenedor. Es cierto que la huella de software de las imágenes slim sigue siendo bastante alta (89 frente a 17 de alpine), lo que implica una superficie de vulnerabilidades mayor (28 en slim frente a 0 en alpine).
Vulnerabilidades de seguridad
Las vulnerabilidades son una preocupación importante y han sido el tema central de muchos artículos sobre por qué deberías reducir el tamaño de tus imágenes de contenedor. Sin embargo, la naturaleza de los problemas de seguridad también importa mucho.
Si dejamos de lado las imágenes node y node:bullseye por su mayor huella de software y el aumento de vulnerabilidades de seguridad, podemos concentrarnos en un conjunto más reducido de tipos de imagen. Al comparar slim, alpine y distroless, la variación en las vulnerabilidades de seguridad altas y críticas no es grande en términos absolutos y oscila entre 0 y 2. Se trata de un riesgo manejable que podría no ser relevante para el caso de uso de tu aplicación.
Soporte y resiliencia
Es muy importante que el equipo de Docker para Node.js pueda priorizar y atender los problemas relacionados con las compilaciones de tus imágenes de contenedor, y que resuelva los incidentes a tiempo. Si usas etiquetas de imagen que no sean las oficiales basadas en Debian, básicamente no podrás marcar este punto como completado en tu lista.
Al usar las etiquetas de imagen node o node:22.1.0-bookworm-slim, ya sea que elijas una imagen con el sistema operativo completo o una versión más ligera con menos dependencias, obtienes la versión más reciente del entorno de ejecución de Node.js. Aunque es un número par (Node.js 22.1.0), al momento de escribir esto aún no forma parte del ciclo de soporte a largo plazo. Esto significa que incluirá versiones nuevas de otros componentes dependientes, como las versiones más recientes del propio npm (conocidas por tener comportamientos nuevos con errores y requerir tiempo para estabilizarse).
¿Cuál es la conclusión?
La imagen de Docker de Node.js ideal sería una versión ligera del sistema operativo, basada en una versión moderna de Debian, con una versión estable de Node.js con soporte a largo plazo vigente.
La elección sería la etiqueta de imagen de Node.js node:lts-bookworm-slim. Prefiero usar etiquetas de imagen deterministas, así que el pequeño cambio que haría sería usar el número de versión subyacente en lugar del alias lts.
La etiqueta de imagen de Docker de Node.js ideal es node:20.13.1-bookworm-slim.
Si trabajas en un equipo DevOps maduro que puede dar soporte a imágenes base personalizadas, mi segunda recomendación sería la etiqueta de imagen distroless de Google, porque mantiene la compatibilidad con glibc para las versiones oficiales del entorno de ejecución de Node.js. Sin embargo, este flujo de trabajo requiere cierto mantenimiento, así que solo lo recomendaría si puedes asumirlo.
Seguridad de contenedores para DevSecOps
Encuentra y corrige vulnerabilidades en contenedores gratis con Snyk.



