Sebastian Gomez
Node.js, Express, Docker y Docker Swarm
Esta 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 general
Producto 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 24, 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.
Una nota sobre orquestación: Docker Swarm sigue incluido en Docker y funciona perfectamente para aprender, pero hoy está en modo de mantenimiento. En producción, el estándar de orquestación es Kubernetes. Usamos Swarm aquí porque es la forma más sencilla de entender los conceptos de réplicas, balanceo y servicios sin la complejidad de Kubernetes. Una vez que domines estas ideas, dar el salto a Kubernetes será mucho más natural.
2. Objetivos de aprendizaje
- 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.
3. Lo que vas a construir
La API tendrá tres rutas principales:
GET /quotesdevuelve todas las frases.GET /quotes/randomdevuelve una frase aleatoria.GET /quotes/:iddevuelve la frase con el identificador indicado.
La imagen Docker expondrá el servicio en el puerto 3000 y estará optimizada para ejecución en ARM/AMD gracias a la base node:24-alpine. Finalmente, el servicio se orquestará en Swarm con réplicas distribuidas.
4. Requisitos previos
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 24.x o el LTS activo vigente (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 v24.x.x (o el LTS que tengas instalado)
npm --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 entorno
- Crea 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.
6. Paso 1, inicializar el proyecto Node.js
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": ">=22.0.0"
},
"dependencies": {
"express": "^5.1.0"
},
"devDependencies": {
"nodemon": "^3.1.10"
}
}Nota pedagógica: definir los scripts desde el inicio te ayuda a ejecutar tareas repetitivas sin memorizar comandos largos.
7. Paso 2, implementar la API con Express
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) || 3000
const app = createApp()
app.listen(PORT, () => {
console.log(`Servicio Random Quotes escuchando en el puerto ${PORT}`)
})8. Paso 3, pruebas locales
Ejecución en modo desarrollo (recarga automática):
npm run devEjecución de producción (sin nodemon):
npm startVerifica con el navegador o con curl:
curl http://localhost:3000/
curl http://localhost:3000/quotes/random
curl http://localhost:3000/quotes/3Atajos didácticos:
- Detén 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.
9. Paso 4, buenas prácticas previas a Docker
- 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_Store10. Paso 5, construcción de la imagen Docker
10.1 Dockerfile explicado
FROM node:24-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:24-alpinees liviana y compatible con Linux, ideal para producción. Si necesitas otra línea LTS, puedes usarnode:22-alpine.npm ciinstala exactamente las versiones definidas enpackage-lock.jsony, con--omit=dev, omite las dependencias de desarrollo (este flag reemplaza al antiguo--only=production, ya obsoleto).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 contenedorImportante: si estás en macOS/Windows con Docker Desktop, el puerto se publica en tu máquina host automáticamente. En Linux, asegúrate de no tener el puerto 3000 ocupado.
11. Paso 6, publicar en Docker Hub
- Crea 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 y publícala:
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:latestConfirma desde la interfaz web de Docker Hub que la imagen se subió correctamente.
12. Paso 7, introducción a Docker Swarm
Docker Swarm es el orquestador nativo de Docker. Permite:
- Crear clústeres con un nodo
manager(orquesta) y múltiples nodosworker(ejecutan contenedores). - Definir servicios con múltiples réplicas distribuidas.
- Balancear carga y reiniciar contenedores automáticamente si fallan.
Recuerda lo que dijimos al inicio: Swarm está en modo de mantenimiento y en producción real lo habitual es Kubernetes. Aun así, sigue siendo la forma más clara de aprender estos conceptos.
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>:2377Verifica 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-serviceConsejo: si actualizas la imagen, usa docker service update --image tuusuario/random-quotes:1.0.1 random-quotes-service.
13. Paso 8, validar el despliegue en Swarm
- 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 final14. Solución de problemas habituales
- El puerto 3000 está ocupado: cambia la variable de entorno
PORT(PORT=4000 npm start) o detén el proceso que lo usa. - `npm install` falla en Docker: asegúrate de copiar
package-lock.jsony de no editarlo manualmente. - `docker build` muy 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.
15. Retos adicionales
- 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.
16. Lista de verificación final
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.
17. Glosario rápido
- 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.
18. Estructura de referencia del repositorio
Repositorio de GitHub:
random-quotes
├── README.md
├── Dockerfile
├── package.json
├── package-lock.json
├── .dockerignore
└── src
├── app.js
├── quotes.js
└── server.jsEso es todo, espero que esta guía te sea de utilidad y la puedas aplicar a algún proyecto que tengas en mente, o que simplemente te haya ayudado a entender cómo encajan Node.js, Express, Docker y Docker Swarm. Déjame un comentario si te sirvió, si quieres añadir alguna opinión o si tienes alguna duda. Y recuerda que si te gustó, también puedes compartirlo usando los links a las redes sociales aquí abajo. Buena suerte.
Sebastian Gomez
Creador de contenido principalmente acerca de tecnología.