Creando una API REST con Express y Docker
Express.js es un marco de trabajo minimalista, flexible y proporciona un robusto conjunto de …
leer másHabiendo explorado los fundamentos de Docker y su poder para desplegar aplicaciones encapsuladas, nos adentramos ahora en el dominio de Docker Compose, una herramienta esencial para el manejo de aplicaciones compuestas por múltiples contenedores. En este artículo desvelaremos cómo Docker Compose simplifica la vida de los desarrolladores, permitiendo la definición, ejecución y gestión de servicios multi-contenedor con facilidad.
La instalación de Docker Compose se puede realizar junto con Docker Desktop para Windows y Mac, mientras que para
Linux, se debe instalar manualmente con un simple apt install docker-compose
en distribuciones derivadas de Debian
como Ubuntu. Una vez instalado, el primer paso es crear un archivo docker-compose.yml
en la raíz de tu proyecto,
definiendo los servicios, redes y volúmenes que tu aplicación necesite. A veces podrás verlos con la extensión .yaml
en lugar de.yml
, ambos son válidos.
En este artículo, profundizaremos en el uso de Docker Compose, una herramienta indispensable para definir y ejecutar
aplicaciones Docker multi-contenedor de manera eficiente. A partir del ejemplo
del capítulo anterior, donde desplegamos
un servidor Nginx usando el comando docker run
.
Docker Compose permite organizar la configuración de nuestros servicios en un archivo YAML, simplificando el proceso de despliegue y gestión de contenedores que forman parte de una misma aplicación. Veamos cómo se estructura este archivo para el caso de nuestro Nginx:
version: '3.3'
services:
mynginx:
image: nginx
ports:
- "8080:80"
restart: always
En este archivo, la clave version
indica la versión de la sintaxis de Docker Compose que estamos utilizando,
mientras
que la clave services
define los servicios que componen nuestra aplicación. En este caso, hemos definido un servicio
llamado mynginx
que utiliza la imagen oficial de Nginx, exponiendo el puerto 80 del contenedor en el puerto 8080 de
nuestro host. Además, hemos especificado que el servicio se reinicie automáticamente en caso de fallo.
El resultado final en la práctica es el mismo, pero como podemos ya intuir, la gestión de la aplicación se simplifica
enormemente. Bastaría con ejecutar el comando docker-compose up
en la misma carpeta donde se encuentra el
archivo docker-compose.yml
para lanzar nuestro contenedor. Podríamos acceder al servidor Nginx en el puerto
8080
tal y cómo hacíamos en el ejemplo anterior a través de la dirección http://localhost:8080
.
Para casos como este en los que gestionaremos una única “aplicación”, puede parecer exagerado. Sin embargo, cuando se trata de aplicaciones más complejas, con múltiples servicios y dependencias, Docker Compose se convierte en una herramienta indispensable. En futuros artículos exploraremos cómo facilita la gestión de aplicaciones más complejas, permitiendo la definición de redes, volúmenes y dependencias entre servicios.
En nuestro último capítulo de la serie
hemos creado una API creada con ExpressJS virtualizada utilizando Docker. Vamos a convertirla a nuestra nueva
metodología con docker-compose
. Antes almacenábamos los datos en memoria por lo que no había persistencia de datos
tras el reinicio, aprovecharemos la flexibilidad de docker-compose
para añadirle después una base de datos a la que
conectaremos para conservar los datos de nuestra aplicación. A continuación el fichero docker-compose.yml
que lanzaría
un contenedor similar al de nuestro ejemplo de la API:
version: '3.8'
services:
app:
image: node:14-alpine
command: sh -c "npm install && node app.js"
volumes:
- ./:/app
working_dir: /app
ports:
- "3000:3000"
environment:
NODE_ENV: development
En este docker-compose.yml
hemos definido un servicio llamado app
que utiliza la imagen oficial de Node.js en su
versión 14 con Alpine Linux. Hemos especificado un comando que instala las dependencias
de nuestra aplicación y arranca el servidor, así como un volumen que monta el directorio actual en el contenedor.
Además, hemos expuesto el puerto 3000 hacia el host y hemos definido una variable de entorno NODE_ENV
con el entorno
de desarrollo de Node (NODE_ENV) establecido con el valor development
.
Para que esto funcione es necesario iniciar el proyecto NPM con npm init -y
y crear un archivo app.js
con el
siguiente contenido, extraído de nuestro capítulo creando una API de ejemplo:
const express = require('express');
const app = express();
const PORT = 3000;
app.use(express.json());
app.get('/', (req, res) => {
res.send('¡Hola Mundo con Express y Docker!');
});
app.listen(PORT, () => {
console.log(`Servidor corriendo en http://localhost:${PORT}`);
});
Ahora utilizaremos una base de datos PostgreSQL para hacer persistentes los datos de nuestra aplicación y que no se
pierdan. Para ello añadimos el siguiente servicio a nuestro docker-compose.yml
y el parámetro depends_on
al
servicio, este hace que la aplicación espere hasta que la base de datos esté lista antes de arrancar:
version: '3.8'
services:
app:
image: node:14-alpine
command: sh -c "npm install && node app.js"
depends_on:
- db
volumes:
- ./:/app
working_dir: /app
ports:
- "3000:3000"
environment:
NODE_ENV: development
db:
image: postgres:13
ports:
- "5432:5432"
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: app
La parte de la base de datos db
es bastante sencilla, basándonos en la imagen oficial
de PostgreSQL en su versión 13, asociamos el puerto 5432 de PostgreSQL en el contenedor (
a la derecha) con el puerto 5432 del host (a la izquierda), en este caso coinciden pero no tiene por qué ser así, como
ya hemos visto antes. Además, hemos definido tres variables de entorno para configurar la base de
datos: POSTGRES_USER
, POSTGRES_PASSWORD
y POSTGRES_DB
. No está de más mencionar que estos datos son de ejemplo y no
deberían utilizarse nunca en producción.
Con esto ya tenemos creada la definición de nuestra API Express en nuestro docker-compose
junto a una base de datos
PostgreSQL que podremos levantar juntos con un simple comando:
docker-compose up -d
El comando -d
es opcional y significa que se ejecutará en segundo plano, por lo que puedes cerrar el terminal desde el
que lo has ejecutado y el servicio seguirá corriendo. Llegados a este punto, podríamos conectar a nuestra base de datos
para ver que realmente se encuentra ahí utilizando un cliente de base de datos como por
ejemplo DBeaver, un programa open source que nos permite conectar a múltiples tipos de bases de
datos, visualizar su contenido y administrarlas. Por ahora, debería contener una base de datos de nombre app
y estar
vacía.
En el próximo artículo, crearemos las tablas de nuestra base de datos para almacenar los de nuestro ejemplo de la API y exploraremos cómo conectar nuestra aplicación. Veremos también un caso práctico y profundo sobre cómo analizar los logs y cómo consultar las diferentes configuraciones que Docker ha definido para nuestros contenedores. Te invito a seguir investigando por tu cuenta y a estar atento a nuestro próximo capítulo de la serie.
Quizá te puedan interesar
Express.js es un marco de trabajo minimalista, flexible y proporciona un robusto conjunto de …
leer másEn el último capítulo conocimos lo básico de Linux, las distribuciones más utilizadas y amigables …
leer másEs crucial entender algunos parámetros comunes en Docker que son esenciales para su administración a …
leer másDe concepto a realidad