Sebastian Gomez
Node.js, Express, Docker y Docker Swarm
Node.js, Express, Docker y Docker SwarmEsta guía te acompaña paso a paso en la creación de una API de frases motivacionales con Node.js y Express, su empaquetado en Docker y el despliegue en un clúster Docker Swarm. Está pensada para trabajarla en clase, pero también sirve como laboratorio autónomo.
1. Panorama generalProducto final: Un servicio HTTP que expone frases en formato JSON, empaquetado en una imagen Docker lista para producción y desplegada en un Swarm con múltiples réplicas.
Tecnologías clave: Node.js 20, Express 5, npm, Docker, Docker Hub y Docker Swarm.
Duración estimada: 3 bloques de 60 minutos (puedes adaptarlo según el ritmo del grupo).
Enfoque pedagógico: primero entendemos los conceptos, luego construimos manualmente, y al final automatizamos con contenedores y orquestación.
Comprender por qué Express es un framework minimalista y cómo estructurar un proyecto desde cero.
Practicar la creación de endpoints HTTP que devuelvan JSON y gestionen errores simples.
Diferenciar dependencias de producción y desarrollo en
package.json.Construir una imagen Docker reproducible, aplicando buenas prácticas como
.dockerignoreynpm ci.Publicar imágenes en Docker Hub y gestionar tags.
Desplegar y escalar una aplicación en Docker Swarm, validando que las réplicas atienden peticiones.
La API tendrá tres rutas principales:
La imagen Docker expondrá el servicio en el puerto 3000 y estará optimizada para ejecución en ARM/AMD gracias a la base node:20-alpine. Finalmente, el servicio se orquestará en Swarm con réplicas distribuidas.
- Conocimientos previos
Fundamentos de JavaScript moderno.
Manejo básico de la terminal y Git.
Conceptos básicos de contenedores (qué es una imagen, un contenedor, etc.).
- Software instalado
Node.js 20.x (descargar desde https://nodejs.org).
Docker Desktop (macOS/Windows) o Docker Engine 26+ (Linux).
Editor de código (VS Code, WebStorm, etc.).
- Cuentas y accesos
Cuenta en Docker Hub (gratuita) si quieres publicar la imagen.
Acceso a una nube o a máquinas virtuales (Amazon EC2, Azure, etc.) para la sección de Swarm.
- Verificación rápida
node --version # debe devolver v20.x.xnpm --version # npm es parte de Node, versión >=10 recomendada
docker --version # Docker Engine >= 26
docker compose version # opcional, recomendadoSi algún comando falla, detente y corrige la instalación antes de avanzar.
5. Preparación del entornoCrea una carpeta de trabajo donde guardarás todo el material del tutorial.
Abre tu terminal y ubícate dentro de esa carpeta.
(Opcional) Inicializa un repositorio Git y haz commit por secciones para registrar tu progreso.
mkdir random-quotes
cd random-quotes
npm init -y
npm install express
npm install --save-dev nodemon
¿Qué sucedió?
npm init -ygenera unpackage.jsoncon valores por defecto.expressse instala como dependencia de producción.nodemonse agrega como dependencia de desarrollo para recargar automáticamente en modo estudio.
Ajusta package.json
Edita el archivo package.json para que contenga metadatos y scripts útiles:
{
"name": "random-quotes",
"version": "1.0.0",
"description": "Educational Express service that serves random quotes and demonstrates Docker & Docker Swarm deployment.",
"main": "src/server.js",
"scripts": {
"start": "node src/server.js",
"dev": "nodemon src/server.js",
"lint": "echo \"No linter configured\" && exit 0",
"test": "echo \"No tests yet\" && exit 0"
},
"keywords": [
"express",
"docker",
"tutorial"
],
"author": "Sebastian Gomez <seagomezar@gmail.com> (http://www.sebasgo.dev/)",
"license": "ISC",
"engines": {
"node": ">=20.0.0"
},
"dependencies": {
"express": "^5.1.0"
},
"devDependencies": {
"nodemon": "^3.1.10"
}
}
Creamos una estructura simple dentro de src/ para separar responsabilidades.
7.1 Archivo src/quotes.js
Contiene los datos y una función que selecciona un elemento aleatorio. Es una forma sencilla (sin base de datos) de tener contenido.
const quotes = [{
id: 1,
text: 'The best way to predict the future is to invent it.',
author: 'Alan Kay'},{
id: 2,
text: 'Simplicity is the soul of efficiency.',
author: 'Austin Freeman'},{
id: 3,
text: 'Continuous improvement is better than delayed perfection.',
author: 'Mark Twain'},{
id: 4,
text: 'Programs must be written for people to read, and only incidentally for machines to execute.',
author: 'Harold Abelson'},{
id: 5,
text: 'If you automate a mess, you get an automated mess.',
author: 'Rod Michael'},{
id: 6,
text: 'First, solve the problem. Then, write the code.',
author: 'John Johnson'},{
id: 7,
text: 'Any fool can write code that a computer can understand. Good programmers write code that humans can understand.',
author: 'Martin Fowler'},{
id: 8,
text: 'Talk is cheap. Show me the code.',
author: 'Linus Torvalds'},{
id: 9,
text: 'Experience is the name everyone gives to their mistakes.',
author: 'Oscar Wilde'},{
id: 10,
text: 'Success is the ability to go from one failure to another with no loss of enthusiasm.',
author: 'Winston Churchill'}]function getRandomQuote () {const index = Math.floor(Math.random() * quotes.length)return quotes[index]}
module.exports = {
quotes,
getRandomQuote
}7.2 Archivo src/app.js
Aquí configuramos Express, definimos middlewares y rutas.
const express = require('express')const { quotes, getRandomQuote } = require('./quotes')function createApp () {const app = express()
app.use(express.json())
app.get('/', (req, res) => {
res.json({
status: 'ok',
message: 'Welcome to the Random Quotes API',
endpoints: {
random: '/quotes/random',
all: '/quotes',
byId: '/quotes/:id'}})})
app.get('/quotes', (req, res) => {
res.json({
count: quotes.length,
data: quotes
})})
app.get('/quotes/random', (req, res) => {
res.json(getRandomQuote())})
app.get('/quotes/:id', (req, res) => {const id = Number(req.params.id)const quote = quotes.find(entry => entry.id === id)if (!quote) {return res.status(404).json({
error: `Quote with id ${id} not found`})}return res.json(quote)})return app
}
module.exports = { createApp }7.3 Archivo src/server.js
Punto de entrada del servicio: crea la app y la expone en un puerto configurable.
const { createApp } = require('./app')const PORT = Number(process.env.PORT) || 3000const app = createApp()
app.listen(PORT, () => {
console.log(`Servicio Random Quotes escuchando en el puerto ${PORT}`)})- Ejecución en modo desarrollo (recarga automática):
npm run dev
- Ejecución “de producción” (sin nodemon):
npm start
- Verifica con el navegador o con
curl:
curl http://localhost:3000/
curl http://localhost:3000/quotes/random
curl http://localhost:3000/quotes/3Atajos didácticos:
Manda detener el servidor con
Ctrl + C.Usa herramientas como Postman o Insomnia para reforzar conceptos de APIs.
Intenta una petición a un ID inexistente (
/quotes/999) para observar el manejo de errores.
Crea un archivo
.dockerignorepara que la imagen sea más ligera.Añade un
.gitignoresi aún no lo tienes (evita subirnode_modules).Documenta en el README los scripts que definiste para que el equipo los recuerde.
Contenido recomendado para .dockerignore:
node_modules
npm-debug.log
.npmrc
.DS_Store
10.1 Dockerfile explicado
FROM node:20-alpine AS base
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "src/server.js"]Puntos clave:
node:20-alpinees liviana y compatible con Linux, ideal para producción.npm ciinstala exactamente las versiones definidas enpackage-lock.jsony omite las dependencias de desarrollo.EXPOSE 3000documenta el puerto, aunque la publicación real se hace condocker run -p.No necesitamos copiar
node_modulesporque se instalan dentro del contenedor.
10.2 Construir y probar la imagen
docker build -t random-quotes:local .
docker images | grep random-quotesEjecuta un contenedor de prueba:
docker run --rm -d -p 3000:3000 --name random-quotes-test random-quotes:local
docker logs random-quotes-test # Verifica que el servidor arrancó
curl http://localhost:3000/quotes/random
docker stop random-quotes-test # Limpia el contenedorCrea un repositorio público en Docker Hub (por ejemplo
tuusuario/random-quotes).Inicia sesión desde la terminal:
docker login- Etiqueta la imagen local con tu repositorio:
docker tag random-quotes:local tuusuario/random-quotes:1.0.0
docker push tuusuario/random-quotes:1.0.0
docker tag random-quotes:local tuusuario/random-quotes:latest
docker push tuusuario/random-quotes:latest- Confirma desde la interfaz web de Docker Hub que la imagen se subió correctamente.
Docker Swarm es el orquestador nativo de Docker. Permite:
Crear clústeres con un nodo manager (orquesta) y múltiples nodos worker (ejecutan contenedores).
Definir servicios con múltiples réplicas distribuidas.
Balancear carga y reiniciar contenedores automáticamente si fallan.
12.1 Preparar el clúster
Asegúrate de tener al menos una máquina (manager). Lo ideal son tres nodos (1 manager, 2 workers) para experimentar.
Instala Docker en cada máquina (consulta la documentación oficial de tu distro).
Inicializa Swarm en el manager:
docker swarm init --advertise-addr <IP-del-manager>- Copia el comando que se imprime para unir workers y ejecútalo en cada máquina worker:
docker swarm join --token <token> <IP-del-manager>:2377- Verifica desde el manager:
docker node ls12.2 Crear una red overlay (opcional pero recomendado)
docker network create --driver overlay random-quotes-net12.3 Desplegar el servicio
docker service create \
--name random-quotes-service \
--replicas 3 \
--publish published=3001,target=3000 \
--network random-quotes-net \
tuusuario/random-quotes:latest12.4 Supervisar el servicio
docker service ls
docker service ps random-quotes-service
docker service logs random-quotes-service
Obtén la IP pública del nodo manager (o del balanceador si usas uno).
Abre
http://<IP-publica>:3001/quotes/randomen tu navegador o concurl.Actualiza varias veces la página para comprobar que las respuestas varían (las réplicas se reparten las solicitudes).
Si tienes varias máquinas, lanza peticiones desde cada una para simular tráfico distribuido.
Cuando termines:
docker service rm random-quotes-service
docker network rm random-quotes-net # si la creaste
docker swarm leave --force # ejecutar en cada nodo, el manager al finalEl puerto 3000 está ocupado: Cambia la variable de entorno
PORT(PORT=4000 npm start) o detén el proceso que lo usa.npm installfalla en Docker: Asegúrate de copiarpackage-lock.jsony de no editarlo manualmente.docker buildmuy lento: Verifica tu conexión; las imágenes base pueden pesar decenas de MB.El servicio en Swarm no arranca: Revisa
docker service logs. Si la imagen no existe, puede que el worker no tenga acceso a Internet o que el nombre esté mal escrito.Workers no se unen al clúster: Abre los puertos 2377 (control), 7946 TCP/UDP (comunicación entre nodos) y 4789 UDP (red overlay) en el firewall.
Agregar una ruta POST para que la API acepte nuevas frases en memoria.
Persistencia: guardar las frases en un archivo JSON o en una base de datos ligera (por ejemplo, SQLite).
Testing: configurar Jest o Vitest y escribir pruebas para los endpoints.
CI/CD: crear un flujo en GitHub Actions que construya y publique la imagen automáticamente.
Observabilidad: integrar un middleware de logging como
morgano métricas con Prometheus.
npm run devfunciona y muestra logs en consola.Los endpoints devuelven JSON correctamente y gestionan IDs inválidos.
La imagen Docker se construye sin errores y el contenedor responde en el puerto 3000.
La imagen está publicada en Docker Hub con al menos un tag semántico.
El servicio se despliega en Swarm con varias réplicas y balancea carga.
Documentaste todos los comandos importantes en tu README o bitácora.
Node.js: entorno de ejecución JavaScript del lado del servidor basado en V8.
Express: framework minimalista para construir APIs y aplicaciones web en Node.
npm: gestor de paquetes que instala dependencias desde el registro oficial.
Docker Image: plantilla inmutable con todo lo necesario para ejecutar una aplicación.
Docker Container: instancia en ejecución de una imagen.
Docker Swarm: modo de clustering y orquestación nativo de Docker.
Replica: cada copia de un contenedor dentro de un servicio de Swarm.
Endpoint: URL expuesta por un servicio que responde a peticiones HTTP.
Repositorio de GithubE
├── README.md
└── random-quotes
├── Dockerfile
├── package.json
├── package-lock.json
├── src
│ ├── app.js
│ ├── quotes.js
│ └── server.js
└── .dockerignoreSebastian Gomez
Creador de contenido principalmente acerca de tecnología.