Aprende Docker y domina el uso de los contenedores

Firtmiracle el
Aprende Docker y domina el uso de los contenedores

Hola en esta ocasión aprenderemos a usar Docker donde voy a explicarte a detalle paso a paso su funcionamiento y los beneficios que nos otorga usar esta tecnologia la cual puede ayudarnos mucho, asi que es tu momento para aprenderla 😃.

Indice y Estructura

Que es Docker? #

Docker es una plataforma de contenedores que nos permite empaquetar, distribuir y ejecutar aplicaciones de manera eficiente consistente en diferentes entornos, ya que proporciona una forma de encapsular una aplicación y todas sus dependencias en un contenedor virtual ligero y autonomo.

Pero para entenderlo mejor debemos saber en ciencia exacta que es un contenedor?..

Que es un Contenedor? #

Un contenedor es una unidad de software autónoma que encapsula una aplicación y todas sus dependencias, incluyendo el código, las bibliotecas y las variables de entorno necesarias para que la aplicación se ejecute de manera consistente en diferentes entornos.

Por que aprender Docker? #

Entendiendo mejor el concepto de que es un contenedor y como funciona docker, entocnes por que deberia aprenderlo?, pues hay muchas razones y algunas de estas serian:

  • Desarrollo y despliegue más rápido: Docker facilita la creación y gestión de entornos de desarrollo consistentes. Puedes empaquetar tu aplicación y todas sus dependencias en un contenedor, lo que permite que todo el equipo de desarrollo tenga exactamente el mismo entorno de trabajo. Además, el despliegue de la aplicación se vuelve más rápido y confiable, ya que no tienes que preocuparte por las diferencias entre los entornos de desarrollo y producción.

  • Portabilidad y compatibilidad: Docker es una tecnología altamente portable. Puedes desarrollar una aplicación en tu máquina local y ejecutarla en cualquier otro entorno que tenga Docker instalado, ya sea en un servidor local, en la nube o en el centro de datos. Esto hace que sea más fácil compartir y distribuir tu aplicación, sin preocuparte por las dependencias o las diferencias en los sistemas operativos.

  • Aislamiento y seguridad: Docker proporciona un entorno aislado para tus aplicaciones. Cada contenedor tiene su propio sistema de archivos y recursos aislados, lo que evita conflictos y problemas de compatibilidad entre aplicaciones. Además, puedes configurar permisos y restricciones para controlar el acceso y proteger tus aplicaciones y datos.

  • Escalabilidad y eficiencia en el uso de recursos: Docker permite escalar tus aplicaciones de manera eficiente. Puedes replicar contenedores y distribuir la carga entre ellos para manejar aumentos en la demanda de manera más rápida y sencilla. Además, los contenedores son livianos y comparten recursos del sistema, lo que maximiza la utilización de los recursos y reduce los costos operativos.

  • Ecosistema y comunidad activa: Docker cuenta con una amplia comunidad de usuarios y desarrolladores. Esto significa que hay una gran cantidad de recursos, documentación, tutoriales y ejemplos disponibles en línea. Además, Docker se integra con muchas otras herramientas y servicios populares, lo que amplía aún más las posibilidades de uso y colaboración.

Almacenamiento de Contenedores #

Los contenedores se almanecenan en repositorios publicos y privados. De los cuales el repositorio publico mas conocido es Docker Hub.

Aqui podemos ejecutar todo tipo de aplicaciones como Python, SQL, Python, Disstribuciones Linux, MongoDB y mucho mas.

Antecedentes #

Antes de usarse los contenedores existian varios problemas a la hora de que un equipo de desarrollo trabajara en conjunto, esto debido a que cada uno de los mienbros podia tener distinto sistemas operativos y de la misma manera al trabajar en una aplicación estos tendrian distintas versiones de la misma instaladas. Esto causaria una serie de problemas con las dependencias necesarias para la ejecución correcta del programa.

Como consecuencia eventualmente el desarrollo de la aplicación termine mal.

Afortunadamente con los contenedores ahora este tedioso proceso podemos llegar a automatizarlo.

Para ello debemos descargar una imagen basada en linux, pero que es una imagen?

Imagen: En Docker, una imagen es un paquete ejecutable y autocontenido que incluye todo lo necesario para ejecutar una aplicación, incluyendo el código, las bibliotecas, las dependencias y las configuraciones necesarias. Se puede pensar en una imagen como una plantilla o un plano a partir del cual se crean los contenedores.

Gracias a esto con solo ejecutar un comando, podemos tener la aplicación corriendo en nuestra maquina, e incluso ademas podriamos tener distintas versiones de esta misma aplicación corriendo en nuestro sistema sin tener ningun conflicto.

Despliegue de Contenedores #

Cuando queremos desplegar o actualizar nuestra aplicación, necesitaremos el codigo , dependencias y archivos de configuración. Esto puede ser propenso al no haberse considerado alguna dependencia y generando problemas de comunica´ción. Estos errores unicamente podremos verlos al momento de desplegar nuestra aplicación, lo cual generaria problemas y necesitariamos volver a una versión anterior para que nuestro sistema sigua en funcionamiento.

Pero gracias a Docker todo esto podemos evitarlo ya que solo debemos construir una imagen cuya unica dependencia sera el Runtime de docker.

Runtime: El runtime de Docker se refiere al componente responsable de ejecutar los contenedores Docker en un sistema operativo. En otras palabras, es el entorno en el que se ejecutan y se gestionan los contenedores.

Y este proceso puede ser automatizado a traves de pipelines como github.

Por lo que un contenedor en simples palabras vendria a ser capas sobre capas de imagenes donde la primera por lo general sera una distribución basada en linux y por lo general esta corresponde a Alpine ya que es una imagen bastante liviana.

Des esta manera tendremos sobre esta una serie de imagenes hasta que podamos llegar a nuestra capa de aplicación.

Diferencias entre Contenedor y Maquina Virtual #

Al ser docker una forma de virtualización es importante tener en claro las diferencias entre docker y una maquina virtual.

Debemos tener claro que la virutalización se basa en 3 capas, las cuales corresponden al hardware, kernel y la aplicación.

  • Hardware: Donde se va ejecutar la virtualización.
  • Kernel: Se encarga de la comunicación con el hardware, siendo responsable de gestionar los recursos del sistema y proporcionar servicios a los programas en ejecución.
  • Aplicaciones: Aplicación de la cual vamos a hacer uso.

De estas capas las VM virtualizan tanto las aplicaciones y el kernel, en consecuencia las imagenes tienen un peso muy alto.

Pero en el caso de Docker, unicamente virtualiza las aplicaciones, ya que en el caso del Kernel lo que hace docker es utilizar el propio kernel del sistema operativo anfitrión.

En consencuencia las imagenes pesan mucho menos, su rendimiento es superior ya que se ejecutan casi instantaneamente. Por lo que desarrollar y desplegar aplicaciones sera mucho mas rapido y optimo.

Instalación #

Para poder instalar Docker si nos encontramos en Windows debemos instalar Docker Desktop que viene a ser una VM linux y nos permitira ejecutar contenedores.

Si deseas descargarla puedes hacerlo desde el enlace oficial:

Si te encuentras en linux simplemente puedes instalarlo ejecutando:

sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io

Docker Hub #

Una vez tengamos ya instalado Docker, vamos a dirigirnos a el repositorio Docker Hub, donde podemos ver todas las herramientas que podemos utilizar.

Podemos ver que podemos usar imagenes de Python, Sql, Ubuntu, Node entre muchas mas.

Si ahora pinchamos en alguna de ellas como Python y deslizamos un poco podemos ver distintas versiones de Python que podemos utilizar.

Y ahora si subimos un poco, veremos un comando donde veremos un comando el cual si lo ejecutamos nos descargara la imagen respectiva en este caso la de Python.

Listar Imagenes #

El primer comando que ejecutaremos sera docker images y este nos monstrara todas las imagenes que hemos descargado en nuestro sistema.

Como podemos ver no nos lista nada debido a que todavia no contamos con ninguna imagen descargada.

Descargar Imagenes #

Par poder descargar una imagen solo debemos de usar el comando docker pull, ahora bien existen dos formas a traves de las cuales podemos hacerlo.

La primera es simplemente ejecutar el comando y seguido el nombre de la aplicación que queremos descargar, esto por defecto nos descargara la ultima versión disponible.

Vemos como docker descarga cada una de las capas que componen nuestra imagen y si en algun caso nosotros vamos a descargar otra imagen cuyas capas ya fueron descargadas antes por otra imagen como la que tenemos mysql, estas capas no volveran a descargarse ya que usaran las que ya tenemos optimizando nuestro espacio en disco.

Si ahora volvemos a listar las imagenes docker images, observamos que esta vez ya contamos con una imagen descargada.

Si miramos con atención observamos que nuestra imagen tiene una serie de etiquetas.

  • REPOSITORY: Indica el nombre de la imagen
  • TAG: Indica la versión utilizada, en este caso usa latest al no haberla especificado.
  • IMAGE ID: Corresponde a un identificador unico de la imagen.
  • CREATED: Cuando fue creada la imagen.
  • SIZE: Espacio usado en disco.

La segunda manera en la que podemos descargar una imagen seria especificar la versión exacta que queremos descargar, para ello debemos agregar con :version la versión de la imagen y ejecutar el mismo comando.

Observamos que esta vez la imagen se nos descargo casi al instante. Si ahora volvemos a listar las imagenes.

Despues de volver a lista vemos que tenemos casi las mismas etiquetas, ya que al especificar la versión en la descarga, usamos la ultima y seria el mismo proceso al no especificar la versión ya que de esa manera tambien nos descarga la ultima versión. Solo que esta vez tenemos dos imagenes pero con distintos valores en el TAG.

Ahora vamos a descargar otra versión mas antigua de mysql.

Listamos nuevamente las imagenes y esta vez ya que descargamos una versión previa observamos la nueva versión en conjunto con un nuevo identificador.

De esta manera podemos descargarnos distintas imagenes con sus respectivas versiones y si queremos ver cuales estan disponibles, solo debemos dirigirnos a Docker Hub y elegir la versión que necesitemos.

Eliminar una Imagen #

Para poder eliminar una imagen previamente descargada, tenemos que ejecutar el comando docker image rm nombre_imagen:version.

Entonces listamos nuevamente las imagenes.

Y ahora procederemos a eliminar las versiones 8.0 y 5.7.

Listamos nuevamente las imagenes disponibles y vemos que se eliminaron con exito las versiones seleccionadas.

Creación de un Contenedor #

Para poder crear un contenedor, primero debemos tener descargada una imagen y ya que previamente estuvimos usando una correspondiente a mysql, pero para realizar una variacion y simplicidad vamos a trabajar con mongodb.

Asi que primero vamos a proceder a descargar la imagen de mongo.

Y volvemos a verificar que la imagen fue correctamente descargada.

Ahora para poder crear nuestro contenedor debemos ejecutar docker create nombre_imagen.

Esto nos devuelve un identidicador del contenedor que creamos.

Ahora tambien podriamos usar el comando docker container create nombre_imagen para crear un contenedor, pero la forma anterior es mejor ya que lo simplifica.

Iniciar un Contenedor #

Una vez ya creado nuestro contenedor, debemos iniciarlo para poder levantar el servicio que tengamos corriendo.

Para ello usamos el comando docker start id_contenedor.

Y de la misma manera nos devuelve el propio identificador como respuesta al ejectar el comando indicandonos que se inicio correctamente.

Listar Contenedores en Ejecución #

Para poder verificar que el contenedor se inicio de manera correcta tendremos que ejecutar el comando docker ps.

Este comando nos muestra un consunto de etiquetas que corresponden:

  • CONTAINER ID: Es el id del contenedor pero simplificado.
  • IMAGE: Indica en que imagen se baso el contenedor para su creación.
  • COMMAND: Es el comando que usa el contenedor para ejecutarse.
  • CREATED: Cuando fue creado el contenedor.
  • STATUS: Indica el estado en el que se encuentra el contenedor.
  • PORTS: Especifica el puerto del que se hace uso para ejecutar el contenedor.
  • NAMES: El nombre del contenedor.

Detener un Contenedor #

Que paso ahora si queremos detener la ejecución del contenedor, usamos el comando docker stop id_contenedor.

Si tratamos ahora a mostrar los contenedores en ejecución, vemos que esta vez no nos devuelve nada debido a que detuvimos el que previamente creamos.

Sin embargo esto no quiere decir que el contenedor se haya borrado ya que este sigue existiendo.

Listar todos los Contenedores #

Anteriomente veiamos como listar los contenedores actualmente corriendo, pero que paso si queremos ver los contenedores que tenemos independientemente si estos esten o no activos solo hay que añadir un parametro al anterior comando docker ps -a.

Y esta vez podemos ver las mismas etiquetas, pero con la variación que ahora STATUS nos muestra un estado Exited (0) 3 min ago el cual nos indica que el contenedor detuvo su ejecución hace un aproximado de 3 min.

Ademas debido a que ahora el contenedor no se encuentra activo, la etiqueta PORTS se encuentra vacia.

Eliminar un Contenedor #

Si queremos eliminar un contenedor previamente creado y el cual no necesitemos, es super sencillo solo tenemos que ejecutar el comando docker rm id_contenedor.

Si litamos ahora el comando que nos lista los contendores disponibles, vemos que fue eliminado exitosamente.

Establecer Nombre a un Contenedor #

Cuando listabamos los contenedores notamos que en la etiqueta NAME se establecia un nombre extraño asignado al contenedor que creabamos, y esto sucede debido a que si nosotros al momento de crear nuestro contenedor no lo establecemos un nombre docker por defecto lo genera aleatoriamente. Pero existe la posibilidad de nosotros establecer un nombre a nuestro contenedor y trabajar en función a el y no siempre usando el ID.

Para ello solo debemos agregar el parametro --name nombre_contenedor, por lo demas el comando seria exactamente igual.

Lo siguiente seria iniciarlo no?, pero ahora que le asignamos un nombre podemos usar este en lugar del ID. Seguidamente tambien vamos a verificar que nuestro contenedor se encuentra activo.

Observamos que tenemos corriendo nuestro contenedor y esta vez con el nombre que le asignamos mongo_container.

Lo ideal seria comenzar a usarlo, y como en nuestro caso corresponde a un servicio de base de datos guardar información. Sin embargo si queremos interactuar con este desde nuestra maquina host no nos sera posible.

y de seguro te preguntaras debido a que si nos nuestro contenedor esta funcionando correctamente?.

Pues esto se debe a que cuando nosotros ejecutamos una aplicación dentro de un contenedor Docker, esta solo puede comunicarse con el mundo exterior a través de la interfaz de red interna del contenedor, lo que significa que no puede acceder directamente a los puertos del host o a otros dispositivos en la red.

Entonces para poder establecer esta comunicación usamos el concepto de Port Mapping.

Ejecutar comandos dentro del Contenedor #

Es importante mencionar que existe la opción de ejecutar comandos dentro de nuestro contenedor que se encutra corriendo, lo que es útil cuando queremos realizar tareas como administración o depuración del mismo.

Para ello usamos el comando docker exec id_contenedor|nombre_contenedor comando.

Como sabemos nuestros contenedores estan basandos en linux asi que podriamos ejecutar como comando una bash y podriamos acceder mediante una terminal al contenedor, para ello vamos a crear un nuevo contenedor usando nuestra imagen de mongo.

Tambien podriamos mostrar la version de mongo que se esta ejecutando.

En otras palabras podemos ejecutar cualquier comando que nos permita interactuar con nuestros contenedores.

Port Mapping #

El Port Mapping nos permite redireccionar solicitudes de un puerto específico en el host hacia un puerto específico dentro del contenedor. Esto hace que la aplicación dentro del contenedor sea accesible desde el host o desde otras máquinas de la red como si estuviera ejecutándose directamente en el host.

Par poder realizar este proceso de manera practica, primero vamos a proceder a eliminar nuestro contenedor.

Ahora vamos a proceder a crear un nuevo contenedor y lo llamaremos de la misma manera que el previo, solo que esta vez para hacer uso del Port Mapping usaremos un parametro mas -p - -ppuerto_host:puerto_contenedor.

Y ahora en la etiqueta PORT vemos que nuestro puerto host 27017 esta mapeado a el puerto 27017 de nuestro contenedor.

Si ahora hacemos exactamente el mismo proceso, pero esta vez no especificamos nuestro puerto hosts, lo que pasara es que Docker lo eligira por nosotros aleatoriamente.

Observamos que esta vez Docker nos asigno aleatoriamente el puerto 49153. Esto no nos conviene mucho ya que nosotros siempre queremos tener el control de que puerto queremos usar, pero no esta de mas saberlo.

Mostrar logs de un Contenedor #

Si queremos listar los logs de nuestro contenedor, para mostrar los registros con información detallada sobre el funcioamiento correcto o erroneo de nuestro contenedor tambien es muy sencillo, solo basta con ejecutar el comando docker logs monbre_contenedor.

Y si ahora queremos listar los logs pero a la vez mantenernos en escucha de nuevos registros, añadiremos al comando el parametro --follow.

Docker run #

Hemos estado viendo diversos comandos cuando usamos Docker, como descargar una imagen, crear y desplegar nuestro contenedor cada uno con su respectivo comando, pero Docker tambien nos da la opción de hacer todo el proceso en conjunto haciendo uso de un solo comando.

Seguro puedes decir entocnes para que aprendi el resto??.. si literalmente evito hacer tanto embrollo, pues hay que recordar que si bien podemos automatizar el proceso con este comando no quiere decir que en todo momento que uso docker voy a realizar la misma acción, ya que muchas veces vamos a realizar distintas operaciones por separado.

Y bueno para realizar esta operación debemos ejecutar el comando docker run y este comando basicamente hara 4 operaciones:

  • Buscar la imagen y si esta no se encuentra la descargara.
  • Crear un Contenedor
  • Iniciar el Contenedor
  • Mostrar los logs en escucha

Para apreciar mejor su funcionamiento, vamos a realizar el proceso entero, asi que vamos a borrar nuestros contenedores actuales.

Realizamos tambien el borrado de las imagenes.

Ahora ejecutaremos el comando docker run nombre_imagen y este lo que hara al no encontrar la imagen, sera descargarla para despues crear y iniciar nuestro contenedor, hasta que finalmente nos muestre los logs en escucha

Y otra cosa si queremos que nos realize todo el proceso, pero no queremos ver los logs, solo tenemos que añadir el parametro -d.

Otra cosa importante es que podemos usar los mismos parametros de antes incluidos en este comando, como asignarle un nombre --name o usar el Port Mapping con -p.

De esta manera tendriamos desplegado nuestro contenendor y listo para poder usarlo.

Conexión con un Contenedor #

Para poder realizar la conexión de nuestros contenedores, vamos a estar usando NodeJS y MongoDB.

Ejecutaremos un aplicativo simple en NodeJS que se pueda conectar con un contenedor que esta ejecutando MongoDB y de este modo al gestionar todo en contenedores no tengamos que descargarnos Mongo en nuestro maquina hosts.

Para ello a continuación te dejare el codigo de el aplicativo.

const express = require("express");
const mongoose = require("mongoose");
const app = express();

const mongoURI =
  "mongodb://firtsmiracle:firtsmiracle123@localhost:27017/universidad?authSource=admin";

mongoose.connect(mongoURI, { useNewUrlParser: true, useUnifiedTopology: true });

const studentSchema = new mongoose.Schema({
  nombre: { type: String, required: true },
  curso: { type: String, required: true },
  correo: { type: String, required: true },
});

const Student = mongoose.model("Student", studentSchema);

app.use(express.json());

// Ruta para crear un estudiante
app.post("/estudiantes", async (req, res) => {
  try {
    const { nombre, curso, correo } = req.body;

    const newStudent = new Student({ nombre, curso, correo });
    const savedStudent = await newStudent.save();

    res.status(201).json(savedStudent);
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: "Error al crear el estudiante." });
  }
});

// Ruta para listar todos los estudiantes
app.get("/estudiantes", async (req, res) => {
  try {
    const students = await Student.find();
    res.json(students);
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: "Error al obtener los estudiantes." });
  }
});

// Ruta para eliminar un estudiante por su ID
app.delete("/estudiantes/:id", async (req, res) => {
  try {
    const estudianteId = req.params.id;

    const deletedStudent = await Student.findByIdAndDelete(estudianteId);

    if (deletedStudent) {
      res.json({ message: "Estudiante eliminado exitosamente." });
    } else {
      res.status(404).json({ message: "Estudiante no encontrado." });
    }
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: "Error al eliminar el estudiante." });
  }
});

const port = 3000;
app.listen(port, () => {
  console.log(`Servidor corriendo en http://localhost:${port}`);
});

Para ello lo primero que tenemos que hacer es dirigirnos a Docker Hub ya que vamos a necesitar una serie de parametros de configuración al momento de crear nuestro contenedor para acceder a nuestra base de datos.

Una vez ahi ya que queremos usar mongo, usamos el buscado, esto nos llevara a otro apartado.

Aqui encontraremos una lista de instrucciones para poder configurar nuestro contenedor.

Hay que recordar que cada imagen que estemos utilizando necesitara distintos parametros de configuración y en algunos casos no necesitaran ninguno, eso ya dependera del tipo de imagen que estemos utilizando.

Como vamos a estar utilizando una base de datos, necesitaremos un usuario y una contraseña, para ello usaremos unas variables de entorno que las encontraras en la mismo enlace.

Ahora vamos a realizar el proceso completo, para poder ejecutar nuestro contenedor con las configuraciones necesarias.

Primero tenemos que tener nuestra imagen descargada de Docker.

Ahora procederemos a crear el contenedor, con las variables de entorno necesarias para conectarnos. Como vimos en Docker Hub para setearlas usamos el parametro -e y usamos adicionalmente los comandos que ya aprendimos previamente.

Ahora que tenemos correctamente desplegado nuestro contenedor de mongo, vamos a ejecutar la aplicación de NodeJS en nuestro sistema.

Vemos que se ejecuta por el puerto 3000 y si vamos en el navegador el servicio esta activo.

Ahora que el servidor esta escuchando vamos a mandar una petición para crear un estudiante.

La isntrucción se ejecuta correctamente y si ahora vemos en el navegador el registro se realizo correctamente.

Podemos probar a ejecutar otro registro.

Validamos nuevamente y vemos que se visualiza correctamente,

Lo mismo si eliminamos algun registro y validamos al listar los cambios.

De esta manera habriamos realizado de manera exitosa una conexión de nuestro aplicativo con un contenedor que ejecuta mongoDB.

Insertar Aplicación en un Contenedor #

Ahora ya validamos que podemos conectar nuestra aplicación con un contenedor, pero es posible que podamos meter nuestra aplicación a un contenedor?

Pues si y para ello primero crear una imagen a partir de un archivo de nombre Dockerfile.

Crear una imagen #

Para poder crear una imagen necesariamente debemos de crear un archivo dockerfile en donde escribiremos todas las instrucciones que necesitara nuestro contenedor para poder crearse.

Ahora debo mencionar que para crear una imagen debemos de basarnos en otra imagen, ya que estamos usando Node pues debemos basarnos en esta misma.

Dockerfile #

Dokerfile: es un archivo de texto plano que contiene una serie de instrucciones que Docker utiliza para crear una imagen de contenedor.

Creamos nuestro archivo Dockerfile y introducimos los siguientes comandos, te explicare para que funciona cada uno mientras escribimos linea por linea.

Primero usaremos FROM imagen:version para indicarle en que imagen nos basaremos, en nuestro caso ya que estamos usando la ultima versión disponible y esta corresponde a la 18, debemos especificarla.

FROM node:18

Despues vamos a crear una carpeta en donde vamos a introdir el codigo fuente de nuestro aplicativo eso lo hacemos con: RUN mkdir -p ruta. Ojo la ruta hace referencia a la del contenedor no a la de tu equipo local, y puedes elegir la que quieras.

FROM node:18
RUN mkdir -p /home/myapp

Ahora tenemos que indicarle la ruta de donde va a introducir el codigo y para ello usamos COPY ruta_origen ruta_destino. Donde especifico en la ruta local un punto ya que tomare como referencia el directorio actual.

FROM node:18
RUN mkdir -p /home/myapp
COPY . /home/myapp

Seguidamente tenemos que exponer un puerto, para que otros contenedores y nuestra maquina host podamos conectarnos a este nuevo contenedor, para esto usamos EXPOSE puerto.

FROM node:18
RUN mkdir -p /home/myapp
COPY . /home/app
EXPOSE 3000

Ahora finalmente debemos indicarle el comando que se debe ejecutar para que la aplicación corra con CMD ["comando", "ruta_archivo"].

FROM node:18
RUN mkdir -p /home/myapp
COPY . /home/app
EXPOSE 3000
CMD ["node", "/home/myapp/index.js"]

Creación de una Red #

Para que nuestros contenedores puedan conectarse, esto debido a que si bien es cierto podemos tener muchos contenedores, no necesariamente todos ellos deben poder conectarse. Y para que nuestro contenedores se puedan conectar debemos crear valga la redundancia una red interna que realize la conexión de dos o mas contenedores.

Todos los contenedores que se encuentren en la misma red, pueden conectarse y podemos crear cuantas redes queramos.

Docker Network #

Para realizar la creación de una red, debemos ejecutar el comando docker network y podemos ver una serie de parametros que podemos utilizar.

Dentro de esos parametros que podemos hacer uso, podemos ver uno que corresponde a ls, el cual nos permitira listar las redes disponibles creadas por defecto.

Vamos ahora a crear nuestra nueva red usando el comando docker network create nombre_red.

Al listar nuevamente vemos que ahora tenemos una nueva red redInterna.

Y si te preguntas podemos eliminar una red pues si para ello usamos docker rm nombre_red.

Ahora cuando los contenedores estan dentro de la misma red se comunican mediante el nombre del contenedor. En otras palabras el nombre de dominio del contenedor va a ser el mismo al nombre que le damos cuando lo creamos.

Por lo tanto debemos de cambiar en nuestro codigo del aplicativo y en vez de localhost indicarle el nombre del contenedor de mongo que llamaremos mongo_connect.

Docker build #

Vamos a proceder a crear nuestra imagen con el comando docker build el cual nos permitira crear una imagen basadas en un archivo docker file.

Este comando recibe dos argumentos, docker build -t nombre_imagen:tag ruta_dockerfile, donde en tag podemos hace referencia a la versión que tendra nuestro proyecto.

Ejecutamos el comando y vemos como se crea nuestra imagen.

Si ahora volvemos a listar nuestras imagenes observamos que ahora tenemos una nueva correspondiente a node.

Como ultimo paso ahora debemos de volver a crear nuestros contenedores y especificarles que estos ahora formaran parte de una red, para ello primero vamos a borrar el contenedor que previamente creamos.

Y ahora vamos a usar los mismo comandos para crear el contenedor, solo que esta vez adicionaremos un nuevo parametro --network nombre_red.

Del mismo modo crearemos el contenedor de nuestra aplicación alojada en la imagen que creamos.

Ojo debemos especificar tambien con -p el puerto ya que vamos a acceder de nuestro equipo, ademas de la red y la iamgen tal cual con el tag que proporcionamos.

Iniciamos ambos contenedores y ahora se encuentran activos.

Nos dirigimos a nuestro navegador para hacer las pruebas respectivas y vemos que genial nuestro aplicativo corre sin problemas.

Vamos a crear un estudiante ahora con la ayuda de curl.

Listamos nuevamente los datos en nuesntro navegador.

Vemos que se ejecuta todo correctamente y asi es como pudimos meter nuestra aplicacion en un contenedor y conectarla con otro.

Docker Compose #

Es una herramienta que permite definir y administrar aplicaciones Docker multi-contenedor. Con Docker Compose, puedes describir toda la configuración de tu aplicación, incluidos los servicios, las redes y los volúmenes que necesita, en un archivo de configuración YAML.

En pocas palabras esto nos va a ahorrar el proceso que hicimos anteriormente para conectar nuestros contenedores que en si fueron muchos pasos y con docker compose podemos automatizarlo todo.

Para usar esto, debemos de crear un archivo yaml de nombre docker-compose.yml.

YAML: Es un formato de serialización de datos legible por humanos. Fue diseñado para ser un lenguaje simple y fácil de entender que permite representar datos estructurados de manera concisa y clara. YAML se utiliza ampliamente como un formato de configuración, especialmente en el contexto de herramientas y aplicaciones de desarrollo y despliegue, como Docker Compose, Kubernetes, Ansible y muchas otras.

Dentro de este archivo le indicaremos primero la versión que usaremos, en este caso version: "3.9"

version: "3.9"

Despues en services indicaremos el nombre de los contenedores que vamos a asignar a los contenedores a usar.

version: "3.9"
services:
  nodeApp:
  mongo_connect:

Dentro de cada nombre indicaremos con build ruta_dockerfile, puertos a mapear ports puerto_hosts:puerto_contenedor y el nombre del contenedor que usara el servicio de nodeApp links nombre_contenedor_usa_servicio.

version: "3.9"
services:
  nodeApp:
    build: .
    ports:
      - "3000:3000"
    links:
      - mongo_connect
  mongo_connect:

De la misma manera similar para el otro contenedor, solo que en este caso especificaremos la imagen en la que se basara image: mongo la cual ya teniamos descargada y le indicaremos las variables de entorno que necesitamos environment las cuales antes establecimos para conectarnos.

version: "3.9"
services:
  nodeapp:
    build: .
    ports:
      - "3000:3000"
    links:
      - mongo_connect
  mongo_connect:
    image: mongo
    ports:
      - "27017:27017"
    environment:
      - MONGO_INITDB_ROOT_USERNAME=firtsmiracle
      - MONGO_INITDB_ROOT_PASSWORD=firtsmiracle123

Listo ahora guardamos nuestro archivo, pero recordemos que tenemos dos contenedores activos de cuando realizamos la conexión, por ello primero vamos a pararlos.

Ya que todo esta listo ejecutamos docker-compose up y esto se encargara de construir nuestros contenedores.

En versiones mas recientes ejecutar docker compose up es exactamente lo mismo.

Vemos que este comando nos automatiza todo el proceso completo y si ahora verificamos si se esta ejecutando el servicio y este se encuentra funcionando correctamente.

Igual podemos realizar peticiones o listar nuevamente y todo funciona de maravilla.

Si ahora queremos detener la ejecución, como casi clasicamente hacemos usando las teclas ctrl_c.

Podemos listar las imagenes que tenemos y observamos una nueva que fue creada ejecutamos docker compose.

y del mismo modo si listamos los contenedores, tambien vemos dos nuevos los cuales tambien se crearon al ejecutar el comando.

Pero esto no acaba aqui, ya que asi como docker compose nos automatiza la creación, tambien podemos eliminar absolutamente todo, tanto los contenedores que creo como la red que se crea por default de manera automatica.

Y este comando es docker compose down o en versiones antiguas docker-compose down.

Listamos nuevamente los contenedores y estos ya no se encuentran.

Asi pudimos aprender lo maravilloso que es docker compose y como este nos simplifica todo el proceso.

Volumenes #

Generalmente cuando estamos trabajando con contenedores, al eliminarlos estos se borran en conjunto con todos los datos que este contiene. Ya sea cuando estamos creando alguna aplicación que constantemente vamos a estar actualizando, seria muy tedioso estar creando una imagen cada vez que hagamos un cambio.

Pero si queremos que a pesar de que cuando eliminemos nuestro contenedor, toda nuestra data permanezca intacta y de la misma manera si vamos a estar desarrollando alguna aplicación evitar el fastidio de estar constantemente creando una imagen. Pues docker tiene una solución a esto y es a traves de los Volumenes.

Con los volumenes los que hacemos es que una parte de los archivos que va a tener nuestro contenedor, podemos montarla en nuestro propio sistema principal. Por ello al encotrarse en nuestro sistema hosts no van a eliminarse.

Esto nos sirve un monton si vamos a trabajar con base de datos o cuando estamos desarrollando algun sistema.

Existen tres tipos de maneras de poder crear nuestro volumenes y podemos usaarlos segun lo veamos conveniente.

  • Anonimo: Indicamos la ruta de lo que queremos almacenar en un volumen y docker se encarga de montarlo en donde lo vea conveniente. Y otro punto es que no vamos a poder referenciar nuestro volumen para que otro contenedor que creemos lo pueda usar.

  • Anfitrion: En este caso nosotros podemos decidir la ruta de donde se va almacenar nuestro volumen.

  • Nombrado: Es muy parecido al volumen anonimo, solo que este tiene una mejora y es que si vamos a poder referenciar nuestro volumen, lo que nos ayudara a poder reutilizarlo.

Bien para poder usar los volumenes, debemos a volver a abrirnos nuestro archivo previamente creado docker-compose.yml.

Vamos a agregar la etiqueta volumes y le asignaremos el nombre del volumen que queremos darle en nuestro caso db-mongo.

version: "3.9"
services:
  nodeapp:
    build: .
    ports:
      - "3000:3000"
    links:
      - mongo_connect
  mongo_connect:
    image: mongo
    ports:
      - "27017:27017"
    environment:
      - MONGO_INITDB_ROOT_USERNAME=firtsmiracle
      - MONGO_INITDB_ROOT_PASSWORD=firtsmiracle123
  volumes:
    db-mongo:

De la misma manera agregaremos otra etiqueta volumes dentro del nombre de nuestro contenedor, donde le indicaremos que volumen va a utilizar, en este caso db_mongo especificando la ruta en donde por defecto mongo guarda los datos /data/db

En caso de que estemos trabajando con una base de datos relacional como mysql, la ruta cambiaria y seria /var/lib/mysql, lo mismo para cualquier gestor de base de datos ya que debemos especificar segun de cual estemos haciendo uso la ruta por defecto donde se almacena la data.

version: "3.9"
services:
  nodeapp:
    build: .
    ports:
      - "3000:3000"
    links:
      - mongo_connect
  mongo_connect:
    image: mongo
    ports:
      - "27017:27017"
    environment:
      - MONGO_INITDB_ROOT_USERNAME=firtsmiracle
      - MONGO_INITDB_ROOT_PASSWORD=firtsmiracle123
    volumes:
      - db-mongo:/data/db
  volumes:
    db-mongo:

Guardamos nuestro archivo y igual que anteriormente ejecutamos docker compose up, se desplegan nuestros contenedores.

Vamos a realizar nuevamente algunas peticiones a nuestro aplicativo.

Verificamos que los datos se insertaron correctamente.

Ahora para validar que nuestro volumen es funcional, vamos a eliminar los contenedores y volveremos a levantar otros nuevos con docker compose y en consecuencia pese a que borramos los primeros con los que interactuamos realizando las peticiones, al desplegar los nuevos podremos seguir trabajando con los mismos datos.

Entonces primero detenemos la ejecución y eliminamos los contenedores.

Ahora vamos a levantar nuevamente otros dos nuevos contenedores.

Finalmente validemos en nuestro navegador si los datos que insetamos antes siguen almancenado y…

Genial todo funciona correctamente y de esta manera vemos el impacto que se tiene al usar los volumenes para tener una persistencia de datos en el tiempo.

Pero esto no acaba aqui, ya que tambien como mencionaba antes los volumenes tambien nos ayudan en el desarrollo de nuestras aplicaciones.

Para ello primero necesitaremos crear un nuevo archivo Dockerfile.dev donde colocaremos todo lo que necesitaremos para el desarrollo, donde vamos a tener un contenido similar a la de nuestro primer dockerfile, pero con algunas variaciones que vamos a ver a continuación.

Si anteriomente has trabajado con Node, debes de haber utilizado una herramienta que nos permite detectar los cambios que vamos realizando y esta es nodemon, asi que vamos a usar un comando el cual va a instalarlo.

FROM node:18
RUN npm i -g nodemon
RUN mkdir -p /home/myapp

WORKDIR /home/app

COPY . /home/app
EXPOSE 3000
CMD ["node", "/home/myapp/index.js"]

Tambien vamos a necesitar indicarle la ruta en la cual vamos a estar trabajando y eso lo hacemos con el comando WORKDIR y por lo tanto en CMD eliminaremos la ruta completa y pasaremos a usar nodemon.

FROM node:18

RUN npm i -g nodemon
RUN mkdir -p /home/myapp

WORKDIR /home/myapp

EXPOSE 3000

CMD ["nodemon", "index.js"]

Bueno, ya que vamos a usar un volumen que se encargara de crear un enlace entre el codigo de nuestra aplicación y la ruta /home/myapp, no necesitamos mas el comando copy.

y a continuación ahora debemos de crear tambien un nuevo archivo de docker-compose-fordev.yml pero vamos a varias un poco el contenido.

Primero, vamos a añadir la etiqueta context indicandole la ruta de la aplicación y ponemos un punto ya que es la ruta actual, despues indicamos con la etiqueta dockerfile el archivo dockerfile que ultilizara para crear el contenedor nodeapp. y finalmente añadimos un nuevo volumen dentro del nodeapp especificando la ruta que sera montada y despues la ruta del contenedor.

version: "3.9"
services:
  nodeapp:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    links:
      - mongo_connect
    volumes:
      - .:/home/myapp
  mongo_connect:
    image: mongo
    ports:
      - "27017:27017"
    environment:
      - MONGO_INITDB_ROOT_USERNAME=firtsmiracle
      - MONGO_INITDB_ROOT_PASSWORD=firtsmiracle123
    volumes:
      - db-mongo:/data/db
  volumes:
    db-mongo:

Ya que tenemos el archivo listo lo guardamos y ejecutamos nuestro comando docker compose pero esta vez añadiremos el parametro -f donde le indicaremos el archivo de docker compose personalizado que en nuestro caso seria este que recien creamos docker-compose-fordev.yml.

Ahora vemos en nuestro navegador que al igual que antes tenemos los datos que previamente creamos.

Ahora vamos a añadir en nuestro codigo una linea en consola para que cuando listemos los estudiantes nos muestre un mensaje Listando todos los estudiantes.

Ahora simplemente al guardar los cambios, automaticamente vemos el cambio reflejado gracias a nodemon.

Pero vamos a validarlo como se debe y vamos a hacer que se ejecute el console que modificamos cuando recargamos la pagina , depaso tambien insertaremos un nuevo registro solo para probar.

Recargamos el navegador.

Toca la prueba de fuego, volvemos a revisar los logs y mira lo que encontramos.

Asi podemos tener un ambiente de desarrollo al usar docker compose y aprendimos a usar esta grandiosa herramienta.

Si te percataste falta algo importante que seguro te estas preguntando, y es la siguiente..

Genial ya se como crear mis volumenes en docker pero y estos donde se almacenan por si quiero trabajar sobre estos?

Pues bueno si estas en linux solo debes dirigirte a la ruta /var/lib/docker/volumes. y indicar el nombre del volumen.

Y ahora si estas usando Mac OS o Windows, basta con abrir docker desktop y dirirte a la sección Volumes.

Y bueno esto seria todo por esta oportunidad, espero haberte ayudado a comprender el funcionamiento de esta grandiosa herramienta y ahora si debes ponerte manos a la obra con los contenedores ya que ahora tienes un nuevo superpoder por que ahora dominas Docker. 😎

Hasta la proxima! 😄😊.

Comments

comments powered by Disqus