Software Crafters® 2025 | Creado con 🖤 para elevar el nivel de la conversación sobre programación en español | Legal
Dockerizar un proyecto Nodejs es bastante sencillo, en este artículo veremos el paso a paso y comentaremos buenas prácticas a tener en cuenta. Docker se ha vuelto muy popular en los últimos años, y no es de extrañar, ha aportado grandes mejoras respecto a las clásicas máquinas virtuales.
Para dockerizar nuestro proyecto necesitamos crear lo que denominamos Dockerfile. Este no es nada más que un archivo que define las instrucciones que debe de realizar Docker para construir el contenedor. Empecemos con uno simple para un proyecto Node.js y vamos añadiendo mejoras.
FROM node:10 WORKDIR /usr/src/app COPY . . RUN npm install CMD ["npm", "start"]
Este Dockerfile simplemente baja la imagen oficial de Node.js, copia nuestro proyecto, instala las dependencias y cuando Docker ejecute el contenedor, lanzará el comando
npm start
.
Este Dockerfile podríamos mejorarlo añadiendo algunas buenas prácticas:
FROM node:10-alpine WORKDIR /usr/src/app # Copiamos primero los ficheros necesarios para instalación de dependencias COPY package*.json ./ # Instalamos dependencias RUN npm ci --only=production # Copiamos el código COPY . . # Definimos la variable PORT en el contenedor ENV PORT=3000 EXPOSE $PORT # Ejecutamos el comando en modo producción ENV NODE_ENV=production CMD ["npm", "start"]
Vamos a comentarlo paso a paso.
Las imágenes base clasificadas con -alpine están basadas en la distribución Alpine Linux project, la cuál es mucho más ligera que la de otros sistemas operativos.
El orden de las instrucciones dentro del Dockerfile es importante por temas de eficiencia. Docker toma de referencia el checksum de una instrucción y sus dependencias (ficheros), si no ha cambiado, es capaz de utilizar la caché guardada. En el caso de un proyecto Node.js:
Aunque docker soporta múltiples comandos
RUN
para instalar un servicio tras otro, es más óptimo juntar los comandos siempre que sea posible mediante el símbolo &&
.
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - && apt-get update && apt-get install -y nodejs
Ejecutar procesos como superusuario es considerado una vulnerabilidad en Docker. Crear un usuario dentro de la imagen y ejecutar la imagen con sus permisos es considerada buena práctica:
# Creamos usuario sin privilegios RUN addgroup -g 1001 -S appuser && adduser -u 1001 -S appuser -G appuser # Actualizar permisos RUN chown -R appuser:appuser /usr/src/app # Cambiar a usuario sin privilegios USER appuser
Usar variables de entorno con
ENV
durante la construcción de la imagen es útil para definir una configuración por defecto en el contenedor. Sin embargo, dado que esas variables quedan en las capas de la imagen, es importante no añadir secrets como tokens, contraseñas, etc. como variables de entorno.
Existen otras formas de añadir información sensible:
docker run -e "NODE_ENV=production" mi-imagen
docker run --env-file .env mi-imagen
env_file: .env
Más información: Docker compose with env_fileMejorar la seguridad y construir una imagen donde no existan vulnerabilidades es un arte, y desde Docker 17.05 existe la posibilidad de usar multistage build para este propósito. Esto permite crear una imagen temporal de construcción donde compilas tu aplicación y luego moverla al contenedor final. Esta técnica permite separar lo que es el proceso de construcción de la imagen final de producción:
FROM node:10-alpine as build WORKDIR /usr/src/app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM node:10-alpine WORKDIR /usr/src/app # Copiamos dependencias y el build de la fase anterior COPY --from=build /usr/src/app/dist dist COPY --from=build /usr/src/app/node_modules node_modules COPY package*.json ./ ENV PORT=3000 EXPOSE $PORT # Creamos usuario sin privilegios RUN addgroup -g 1001 -S appuser && adduser -u 1001 -S appuser -G appuser # Actualizar permisos RUN chown -R appuser:appuser /usr/src/app # Cambiar a usuario sin privilegios USER appuser ENV NODE_ENV=production CMD ["npm", "start"]
De esta forma, nuestra imagen final solo tiene aquellos ficheros necesarios para ejecutar nuestra aplicación, dejando fuera todo el código fuente, ficheros de test, etc.
De la misma forma que gitignore, que ayuda a evitar subir ciertos ficheros o carpetas al repositorio, Docker cuenta con un fichero denominado .dockerignore que excluye ficheros y directorios a la hora de copiar el código en la instrucción COPY del Dockerfile.
Los ficheros excluidos típicamente son la carpeta node_modules, la carpeta .git, ficheros de configuración del IDE, archivos locales, variables de entorno, ficheros de log, etc.
.git node_modules .vscode .DS_Store coverage .env* *.log
Esto además ayuda a que la construcción de la imagen sea más rápida ya que tiene que copiar menos ficheros.