Docker nos permite ejecutar de forma aislada, aunque esto realmente es un truco, ya que Docker lo que hace es usar tecnologías como Cgroups
(permite poner límites de recursos a procesos por ejemplo de memoria, CPU, etc) y namespaces
(permite definir qué es lo que puede ver cada proceso) para que parezca que se ejecuta todo en una maquina independiente.
Una de las ventajas de Docker es la rapidez en comparación con una VM, podemos disponer de un sistema Ubuntu en cuestión de segundos.
En Docker disponemos de diferentes herramientas que podemos utilizar:
- Docker Engine: Herramienta para gestionar las imágenes, contenedores, red, etc.
- docker-compose: Se trata de una forma de conectar diferentes contenedores, hablaremos de él más adelante.
- Docker Hub: Es como GitHub, una plataforma en la que podemos crear, administrar y subir nuestras imágenes.
Dockerizar un proyecto Node.js es bastante sencillo, en este artículo veremos el paso a paso y comentaremos buenas prácticas a tener en cuenta.
Para este artículo he creado un repositorio en el que podrás comprobar y descargarte los diferentes ejemplos, en enlace para cada uno lo encontrarás junto a 👩💻.
Tras esta breve intro vamos al lio!
Step 1: Dockerizando un API Node.js simple
Lo primero es crear un fichero Dockerfile
en la raíz del proyecto, en este fichero vamos a indicar los mismos pasos que realizamos nosotros para construir nuestra API de forma manual, de modo que Docker sea capaz de hacerlo automáticamente.
⚠️ esto lo que podemos encontrar en varios artículos de dockerización de un proyecto Node.js.
FROM node
WORKDIR /usr/src/app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["node", "server.js"]
👌 Good practice: version
Lo mejor a la hora de crear nuestro Dockerfile
, desde otra imagen, es indicar siempre la versión que queremos utilizar, de modo que nos aseguramos que ésta es compatible con nuestra API.
# ❌
FROM node
# 👌
FROM node:14.4.0
👌 Good practice: .dockerignore
Solo debemos copiar lo necesario para desplegar nuestro proyecto. Con el Dockerfile
anterior podemos observar lo siguiente:
Si añadimos un fichero a nuestra carpeta node_modules
podemos comprobar que éste se copiará también en nuestro contenedor, por ejemplo testFile.txt
.
# Construimos la imagen
➜ docker build -t node-test .
# Arrancamos la imagen
➜ docker run node-test
# Contenedores
➜ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6418ce58579f node-test "docker-entrypoint.s…" 5 seconds ago Up 5 seconds 3000/tcp brave_mahavira
# Accedemos al contenedor
➜ docker exec -it 641 bash
# Comprobamos
root@6418ce58579f:/usr/src/app# cd node_modules/
root@6418ce58579f:/usr/src/app/node_modules# ls
De igual modo revisando el contenedor podemos ver que en el WORKDIR
del mismo se han copiado algunos ficheros y directorios como: .git
.gitignore
.idea
, que no son necesarios.
root@6418ce58579f:/usr/src/app# ls -la
.dockerignore
se comporta igual que el .gitignore
. Esto ayuda a que las imágenes que creamos sean más ligeras y seguras.
👌 Good practice: order and layers
Es de vital importancia tener en cuenta como Docker genera las capas, y el orden en que estas se ejecutan.
Usando el ejemplo anterior vamos hacer una comparativa de dos Dockerfiles
.
FROM node:14.4.0
WORKDIR /usr/src/app
# ❌
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
En éste tenemos 5 capas (5 sentencias), y cada vez que modifiquemos algo en nuestro proyecto tanto el COPY
como las siguientes sentencias se volverán a ejecutar (Docker no puede cachear estas capas, ya que han cambiado).
En el siguiente Dockerfile
tenemos 7 capas (7 sentencias) que al disponerlas en el orden que se indica, nos aseguramos de no ejecutar sentencias ya cacheadas innecesariamente.
FROM node14.4.0
WORKDIR /usr/src/app
# 👌
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Aunque modifiquemos nuestro código, siempre que no modifiquemos algunos de los package*.json
, no se ejecutará, ni la copia de los mismos, ni el correspondiente npm install
.
Ahorrándonos mucho tiempo de construcción.
Una vez tengamos el Dockerfile
lo ejecutamos de la siguiente manera:
➜ docker run -p 3001:3000 node-test
👩💻https://github.com/yodra/movies-api
Step 2: Usando Multistage
En el proceso de desarrollo existen diferentes fases para obtener el código que finalmente se desplegará en producción. Docker proporciona multistage, que permite poder ejecutar cada fase en diferentes contenedores.
A modo de ejemplo hemos implementado la misma API de Movies en TypeScript que necesita ser transpilada antes de correr en producción.
Por lo que ahora tendríamos el siguiente Dockerfile
:
FROM node:14.4.0 as trasnpiledApi
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Deploy
FROM node:14.4.0
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --production
COPY --from=trasnpiledApi /usr/src/app/dist .
EXPOSE 3000
CMD ["node", "server.js"]
👌 Good practice: Multistage
Separando fases como compilación, transpilación, empaquetado… nuestras imágenes de producción serán más ligeras y seguras.
👩💻 https://github.com/yodra/movies-api/tree/multistage
Step 3: Añadimos docker-compose
Ya tenemos dockerizada la API y solo nos queda conectarla con una base de datos, para conectar diferentes contenedores entre si utilizamos docker-compose
. Éste es un fichero de configuración .yml
que se encargará de orquestar como se arrancaran cada uno de los contenedores que necesitamos además de conectarlos entre si.
En este fichero especificamos cada uno de los servicios con los que contaremos y las dependencias entre ellos.
version: '3.8'
services:
app:
build: .
ports:
- 3000:3000
depends_on:
- database
database:
image: mongo:3.6.18-xenial
expose:
- 27017
volumes:
- mongodata:/data/db
volumes:
mongodata:
👌 Good practice: identical as production
Esto nos permite disponer de la misma configuración en los diferentes entornos. Trabajar en un entorno igual que el de producción nos permite adelantarnos a posibles errores.
👩💻https://github.com/yodra/movies-api/tree/docker-compose
Resumen de comandos
Aquí algunos de los comandos que podemos necesitar a la hora de trabajar con docker-compose
.
Docker es una herramienta que facilita el desarrollo y todos deberíamos aplicar en nuestros proyectos, a modo de guía he creado una chuleta, la cual incluye un resumen de lo comentado en este artículo, que puedes encontrar en mi web https://yodralopez.dev/
Descrubre nuestro e-book
Si quieres continuar mejorando como desarrollador Javascript te recomendamos nuestro e-book de Clean Code, SOLID y Testing aplicado a JavaScript .
Profundizamos en temas como la deuda técnica y cuales son los tipos, Clean Code desde el punto de vista de mejorar la legibilidad, SOLID para obtener un código más intuitivo y tolerante a cambios, y Unit testing para obtener proyectos de mayor calidad y seguridad... Además, puedes empezar a leer los primeros capítulos gratis.
