Dockerizando Node

¿Cómo manejar volúmenes en Docker?

Maria Garcia-Ch
Nowports Tech and Product
7 min readJun 21, 2021

--

Cuando empezamos a profundizar en temas de Docker y manejo de contenedores, surgen ciertas dudas sobre qué cosas aplican en desarrollo y cuáles aplican en producción. Un tema en particular es el manejo de volúmenes.

La mayoría de los cursos sobre Docker te mencionan un happypath sobre cómo crear un contenedor utilizando cierto sistema operativo y el manejo de volúmenes para Bases de Datos (BD) locales; pero sabemos que, en muchos casos, en temas de producción las BD se conectan a servicios externos como AWS. ¿En qué otros casos pudiéramos utilizar volúmenes?

Para este articulo, haremos uso de volúmenes creando una aplicación api-rest utilizando las imágenes de Node y Mysql, considerando el proyecto para un ambiente de desarrollo. Nos auxiliaremos de los volúmenes para la instalación de los paquetes de Node, el código que estamos trabajando del diario y, por supuesto, para la BD.

Configurando la aplicación ExpressJS

Creamos un nuevo directorio llamado dockerNode y navegamos hasta este. Los comandos son los siguientes.

Ahora, ejecutaremos el siguiente comando para iniciar el proyecto.

Dentro del directorio dockerNode, nuestro proyecto contendrá los siguientes archivos y carpetas.

Ahora trabajemos con el app.js el cual contendrá el siguiente código.

Luego, instalaremos Express+Nodemon y Mysql ejecutando las siguiente líneas en terminal:

Express.js es un framework para Node.js que sirve al momento de crear aplicaciones web. Con la ayuda de este framework, las API se pueden crear de manera muy fácil y rápida. Nodemon por otro lado, es un paquete que escucha los cambios en nuestra aplicación y reinicia el servidor Node en cada cambio que hagamos y guardemos.

Hasta este punto, el package.json del proyecto luce de la siguiente manera:

Para comprobar que el proyecto se ha configurado correctamente, puedes ejecutar en línea de comandos node app.js y revisar en el explorador la ruta http://localhost:3000/ la cual debe mostrar el texto “Hello World”.

Con estos pasos hemos creado una aplicación de Node utilizando el framework de Express. El siguiente es dockerizar la misma para que pueda correr dentro de un contenedor.

Dockerizando

Ahora, configuraremos una imagen personalizada para Node.js, la cual permita que nuestra aplicación pueda vivir dentro de un contenedor. En archivo Dockerfile, escribiremos el siguiente script.

Es importante mencionar que las imágenes de Docker trabajan con capas. En este proyecto se tienen definidas 9 capas, donde cada línea de código representa una capa. El código anterior realizará las siguientes tareas por capa:

  • La primer capa se encarga de descargar la versión 11.15.0 de Node del Dockerhub.
  • Creará un directorio de trabajo llamado app.
  • Copiará el archivo package.json del directorio desde nuestra maquina, al directorio de la aplicación que vivirá en un contenedor de Docker. Esta capa es muy importante, ya que debemos dejarle la instalación de los paquetes a Docker y por ningún motivo realizarla manualmente con un npm i desde nuestra terminal; ya que esto podría crear conflictos con paquetes que corran dentro de nuestro S.O. Por ejemplo, si trabajamos estos con Windows, pudieran aplicar de manera distinta al S.O. que corre Node en Docker, que en este caso es Debian.
  • Instalará todos los paquetes del archivo package.json en el directorio de la aplicación.
  • Copiará el contenido del directorio actual de nuestro sistema al directorio de la aplicación del contenedor.
  • Luego, ejecutará el archivo app.js, es decir, pondrá en funcionamiento la aplicación.
  • Finalmente con la instrucción EXPOSE, la aplicación escuchará en el puerto 3000 en tiempo de ejecución. Para que esto último sea posible, una vez que la imagen se encuentre corriendo en un contenedor, el puerto 3000 será mapeado a alguno de nuestra red local.

Para el caso de la capa 3, donde se define un directorio de trabajo(WORKDIR /app), este apunta a todo nuestro repositorio y, aunque modifiquemos alguna sección del código, esto no provoca que se ejecute constantemente la instalación de los paquetes; de hecho, se realiza en la siguiente capa.

Esto es posible debido a que instalación de paquetes precisamente se hace en una capa separada y empleando el comando COPY. En un Dockerfile, le permite importar uno o más archivos externos a una imagen de Docker. Los comandos COPY siempre se ejecutan para tener la última versión del archivo externo, con esto las capas posteriores al COPY sé ejecutarán siempre y cuando el package.json sufra algún cambio.

Teniendo en cuenta estas notas, ahora construiremos la imagen utilizando el siguiente comando.

Para ver en ejecución nuestra aplicación en un contenedor utilizamos la siguiente instrucción en línea de comandos.

En este punto, se ha definido un contenedor manualmente, el cual utiliza la imagen mycustomimage creada previamente y se mapeó el puerto 3000 de la imagen al puerto 3000 de nuestra computadora. De esta manera, ahora puedes revisar en tu explorador sobre el mismo puerto tu aplicación en funcionamiento.

Adicionalmente, también puedes ejecutar el comando en terminal docker ps para ver el listado de los contenedores y cierta información de ellos. Como respuesta al comando, se debería mostrar una salida similar a la que se muestra a continuación.

Para detener la imagen, utiliza en una terminal externa a la que corre el contenedor, el comando: docker stop containerID

Las configuraciones que se pueden realizar en un contenedor son extensas, por ejemplo, podemos configurar un nombre de contenedor, dependencias a otros contenedores, manejo de volúmenes, entre muchas otras posibilidades.

Realizar esto repetidamente en línea de comandos cada que necesitamos levantar nuestro contenedor resulta tedioso y dificulta el compartir con otros desarrolladores. Para hacer esta tarea más sencilla, entra en juego Docker-compose, además de permitirnos tener un histórico de nuestro proyecto.

En la siguiente sección utilizaremos docker-compose para realizar todas las configuraciones de nuestro contenedor.

Docker-compose

Como se mencionó al inicio de este articulo, el manejo de volúmenes es una de las configuraciones que más nos causa confusión de un contenedor, y no estamos hablando para el manejo de Base de Datos, sino para otros puntos.

Partimos de la creación de una imagen personalizada de Node.js para ser utilizada dentro de un contenedor. Si bien pudimos utilizar directamente Node dentro de nuestro contenedor, esto nos traería ciertas desventajas.

Construiremos a continuación el archivo docker-compose.yml e iremos explicando algunos de estos puntos al construir el contenedor de esta manera.

El código anterior realizará las siguientes tareas:

  • Definir un servicio llamado api
  • Definir dentro del servicio el contenedor de nombre apicontainer
  • Construir una imagen utilizando el archivo Dockerfile
  • Realizar el mapeo del puerto 3000 de la imagen al puerto 3000 de nuestra PC
  • Definir el volumen de trabajo ./app mapeado a nuestro equipo, lo cual permite estar modificando los archivos desde nuestro ID y sabemos que también cambian en la imagen actual
  • Definir un volumen para la instalación de paquetes, es decir, la próxima vez que se levante el proyecto no se hará la instalación de los mismos
  • Utilizar el comando npm run dev para poner en ejecución la aplicación.

Para tener en funcionamiento nuestro contenedor, ejecutamos en terminal lo siguiente.

Ampliando el contenedor con un servicio de BD

Ahora sí, utilicemos el volumen tradicional, Mysql para este caso.

En este caso, el docker-compose realizará adicionalmente las siguientes tareas respecto al la BD:

  • Definir un servicio llamado db
  • Definir dentro del servicio el contenedor de nombre mysqlcontainer
  • Construir una imagen utilizando la imagen del docekerhub mysql:5.7
  • Definir un comando para iniciar la BD
  • Definir las variables de ambientes requeridas para la construcción de la base de datos, estos incluyen principalmente el nombre que contendrá la BD así como usuarios y accesos
  • Definir un volumen para guardar la BD de Mysql que apunta a un directorio dentro de nuestra computadora
  • Mapear el volumen 3306 que Mysql tiene como default al 3306 de nuestra computadora.

Y configuraciones adicionales:

  • Definir una red de nombre development, con la finalidad de que se puedan comunicar el servidor de db con api.
  • La línea external permite accesos externos a la BD con algún ID de trabajo, como Robo3t, TablePlus, entre otros.

Para mas detalles de esta implementación consulta el repositorio aquí.

Conclusión

Como se mencionó en un inicio, en temas de desarrollo, aprendemos de volúmenes para hacer persistentes las Bases de Datos. En Docker existen 3 tipos de volúmenes: host, Anonymus y named volume. Cada uno de ellos tiene sus particularidades de configuración y características. En nuestro caso, utilizamos volúmenes nombrados (es decir, nosotros definimos el nombre del directorio).

A lo largo de este artículo, el uso de volúmenes se empleó en otros casos prácticos, como la instalación de paquetes de Node (node_modules). De esta manera, cada que se levanta el contenedor no se requiere hacer la instalación de paquetes nuevamente.

Otro caso en el que también se utilizó un volumen es para el código mismo de la aplicación. Esto permite que sea como un espejo del contenedor a nuestra máquina local, permitiendo modificar el código directamente desde nuestro ID de desarrollo.

Muchas fuentes no utilizan una imagen personalizada para Node, y el enfoque también es correcto. Utilizar la imagen de esta manera nos proporcionó la ventaja de definir previamente una capa con la instalación del package.json, que en futuro enlazamos a un volumen haciendo los datos persistentes.

En este caso, son pocas las dependencias e instalaciones del package.json, pero a medida que estos crecen, aumenta la duración de instalación y, por ende, el tiempo que toma en tener en producción un contenedor. Por tanto, manejar un volumen para guardar esta información resulta en tiempos más eficientes.

--

--