Introducción: de lo simple a lo complejo en el mundo de la contenerización
\n\nA nivel inicial, trabajar con Docker y JavaScript (Node.js) a menudo se reduce a crear un Dockerfile simple y ejecutar un contenedor. Sin embargo, en sistemas de producción reales, esto no es suficiente. El nivel avanzado implica una comprensión profunda de los mecanismos de compilación, la optimización del tamaño de las imágenes, la gestión de dependencias, el aislamiento de red y la seguridad.
En este artículo, analizaremos los aspectos clave de la contenerización profesional de aplicaciones Node.js. Aprenderá a utilizar compilaciones multi-etapa (multi-stage builds), configurar Docker Compose para una arquitectura de microservicios, gestionar variables de entorno y minimizar la superficie de ataque. El material está dirigido a desarrolladores que ya están familiarizados con los conceptos básicos de Docker y JavaScript.
\n\n1. Compilación multi-etapa (Multi-stage builds) para Node.js
\n\nUno de los principales problemas al contenerizar aplicaciones Node.js es el tamaño excesivo de la imagen final. El enfoque estándar con un solo Dockerfile incluye herramientas de compilación (TypeScript, Webpack, Babel) que no son necesarias en tiempo de ejecución. La compilación multi-etapa permite dividir el proceso en etapas: compilación y ejecución.
1.1. Ejemplo de Dockerfile con dos etapas
\n\n# Etapa de compilación (build)\nFROM node:20-alpine AS builder\nWORKDIR /app\nCOPY package*.json ./\nRUN npm ci --only=production\nCOPY . .\nRUN npm run build # Se asume que el script build compila el proyecto\n\n# Etapa de ejecución (production)\nFROM node:20-alpine AS production\nWORKDIR /app\n# Copiamos solo node_modules y los archivos compilados\nCOPY --from=builder /app/node_modules ./node_modules\nCOPY --from=builder /app/dist ./dist\nCOPY package*.json ./\n\nEXPOSE 3000\nUSER node\nCMD ["node", "dist/index.js"]\n\nPuntos clave:
\n- \n
node:20-alpine— imagen ligera basada en Alpine Linux (aproximadamente 30 MB). \n npm ci— instalación limpia de dependencias estrictamente segúnpackage-lock.json. \n --only=production— excluimos dependencias de desarrollo (TypeScript, pruebas). \n USER node— abandono del usuario root por seguridad. \n
Como resultado, la imagen final contiene solo los archivos necesarios para la ejecución, y el tamaño se reduce de 300-400 MB a 80-120 MB.
\n\n1.2. Optimización del almacenamiento en caché de capas
\n\nDocker almacena en caché cada capa (comando). Para acelerar las compilaciones repetidas, copie package*.json antes de copiar todo el código fuente. Si las dependencias no han cambiado, la capa con npm ci se tomará de la caché.
COPY package*.json ./\nRUN npm ci --only=production\nCOPY . . # Se copia solo si los archivos fuente han cambiado\n\n2. Docker Compose para arquitectura de microservicios
\n\nEn producción, una aplicación Node.js rara vez funciona de forma aislada. Por lo general, se requiere una base de datos (PostgreSQL, MongoDB), una caché (Redis) o una cola de mensajes (RabbitMQ). Docker Compose permite describir toda la infraestructura en un solo archivo YAML.
\n\n2.1. Ejemplo de docker-compose.yml para Node.js + Redis
\n\nversion: '3.8'\nservices:\n app:\n build:\n context: .\n dockerfile: Dockerfile.prod\n ports:\n - "3000:3000"\n environment:\n - NODE_ENV=production\n - REDIS_URL=redis://redis:6379\n depends_on:\n - redis\n networks:\n - backend\n\n redis:\n image: redis:7-alpine\n ports:\n - "6379:6379"\n volumes:\n - redis_data:/data\n networks:\n - backend\n\nvolumes:\n redis_data:\n\nnetworks:\n backend:\n driver: bridge\n\nAspectos importantes:
\n- \n
- Aislamiento de red: los servicios se comunican a través de la red interna
backend. Redis no es accesible desde el exterior (a menos que se exponga el puerto). \n - Volúmenes con nombre:
redis_dataguarda