Aviso Legal
® Software Crafters es una marca registrada.
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:
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!
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"]
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
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.
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
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"]
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
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:
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
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/