Introducción: del monolito a los microservicios con Docker y JavaScript
El desarrollo web moderno con JavaScript (especialmente en el ecosistema Node.js) es impensable sin la contenerización. Docker ha dejado de ser solo una herramienta para "empaquetar" aplicaciones: es el estándar de facto para la entrega, el escalado y la gestión de microservicios. Si ya dominas los Dockerfile y docker run básicos, es hora de pasar al nivel avanzado.
En este artículo, analizaremos cómo crear imágenes Docker eficientes, seguras y ligeras para Node.js, configurar entornos multicontenedor con Docker Compose, organizar la comunicación entre servicios a través de redes y gestionar correctamente los datos mediante volumes. El material está dirigido a desarrolladores que desean construir arquitecturas listas para producción.
1. Optimización del Dockerfile para Node.js: capas, caché y seguridad
El Dockerfile clásico para Node.js suele verse así: FROM node:18, COPY . /app, RUN npm install. Sin embargo, este enfoque genera imágenes enormes (más de 1 GB) y compilaciones lentas. La optimización avanzada se basa en tres principios:
1.1 Compilación en múltiples etapas (Multi-stage builds)
Divide el proceso en una etapa de compilación (con compiladores y dependencias de desarrollo) y una etapa de ejecución (solo runtime). Esto reduce la imagen final entre 5 y 10 veces.
# ---- Etapa de compilación ----FROM node:18-alpine AS builderWORKDIR /appCOPY package*.json ./RUN npm ci --only=productionCOPY . .RUN npm run build
# ---- Etapa de ejecución ----FROM node:18-alpineWORKDIR /appCOPY --from=builder /app/dist ./distCOPY --from=builder /app/node_modules ./node_modulesEXPOSE 3000CMD ["node", "dist/main.js"]1.2 Uso de .dockerignore y caché de capas
Docker almacena en caché las capas. Para evitar reconstruir npm install con cada cambio de código, copia package.json por separado. Añade un .dockerignore:
node_modules.git*.md.envdistAhora npm install solo se ejecuta cuando cambian las dependencias, no todo el proyecto.
1.3 Seguridad: usuario no root e imagen base mínima
Usa node:18-alpine (o incluso scratch con un binario compilado mediante pkg). Nunca ejecutes Node.js como root:
RUN addgroup -S appgroup && adduser -S appuser -G appgroupUSER appuserEsto evita la escalada de privilegios en caso de que la aplicación sea comprometida.
2. Docker Compose: aplicación multicontenedor (Node.js + Redis + Nginx)
Una aplicación real rara vez consta de un solo servicio. Docker Compose permite levantar toda la infraestructura con un solo comando docker-compose up. Veamos un ejemplo avanzado: API en Node.js, Redis para caché y Nginx como reverse proxy.
2.1 Estructura de docker-compose.yml
version: '3.8'services: app: build: context: ./app dockerfile: Dockerfile.prod ports: - "3000:3000" environment: - REDIS_URL=redis://redis:6379 - NODE_ENV=production depends_on: - redis restart: unless-stopped
redis: image: redis:7-alpine volumes: - redis-data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 3s retries: 3
nginx: image: nginx:alpine ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro depends_on: - app
volumes: redis-data:2.2 Configuraciones avanzadas: healthcheck y dependencias
depends_on garantiza el orden de inicio, pero no espera a que el servicio esté listo. Usa healthcheck para Redis y un script de espera en la aplicación:
// En index.js de Node.jsconst co