Docker y TypeScript: contenerización avanzada de aplicaciones
\n\nLa contenerización de aplicaciones TypeScript con Docker no es solo empaquetar código en una imagen. A nivel avanzado, es un arte de optimización, seguridad y automatización. En este artículo, analizaremos cómo crear imágenes Docker eficientes para TypeScript, gestionar el entorno e integrar la contenerización en un pipeline de CI/CD. Aprenderá a reducir el tamaño de la imagen entre 5 y 10 veces, acelerar la compilación y mejorar el aislamiento de dependencias.
\n\n1. Compilación multi-etapa (Multi-stage builds) para TypeScript
\n\nEl problema clásico de las imágenes Docker para TypeScript es incluir en la imagen final todas las herramientas de desarrollo: el compilador de TypeScript, las dependencias de desarrollo y los archivos fuente. Esto genera tamaños enormes (a menudo > 1 GB). La solución es la compilación multi-etapa.
\n\nLa idea es simple: la primera etapa (build) compila TypeScript a JavaScript e instala todas las dependencias. La segunda etapa (production) copia solo el JavaScript listo y las dependencias de producción.
\n\n# Etapa 1: Compilación\nFROM node:20-alpine AS builder\nWORKDIR /app\nCOPY package*.json ./\nRUN npm ci --only=development\nCOPY . .\nRUN npm run build\n\n# Etapa 2: Producción\nFROM node:20-alpine AS production\nWORKDIR /app\nCOPY --from=builder /app/dist ./dist\nCOPY --from=builder /app/package*.json ./\nRUN npm ci --only=production\nEXPOSE 3000\nCMD ["node", "dist/index.js"]\n\nEste enfoque reduce el tamaño de la imagen de ~800 MB a ~150 MB. Observe --only=development y --only=production — son banderas clave para separar las dependencias.
2. Optimización del almacenamiento en caché de capas de Docker
\n\nCada instrucción en un Dockerfile crea una capa. Docker almacena en caché las capas si no han cambiado. El orden correcto de las instrucciones es fundamental para la velocidad de las compilaciones repetidas.
\n\nEjemplo incorrecto (rompe la caché):
\nFROM node:20-alpine\nWORKDIR /app\nCOPY . .\nRUN npm ci\nRUN npm run build\nCMD ["node", "dist/index.js"]\n\nAquí, cualquier cambio en el código fuente (incluso en README.md) invalida la caché para npm ci y la compilación.
\n\nEjemplo correcto (conserva la caché):
\nFROM node:20-alpine AS builder\nWORKDIR /app\nCOPY package*.json tsconfig.json ./\nRUN npm ci --only=development\nCOPY src ./src\nRUN npm run build\n\n¿Por qué funciona? Las dependencias cambian raramente, mientras que el código fuente cambia a menudo. Al copiar package*.json por separado, garantizamos que la capa con npm ci solo se reconstruya cuando estos archivos cambien.
3. Trabajo con variables de entorno y secretos
\n\nPara aplicaciones TypeScript en Node.js, el uso de variables de entorno es un estándar. Sin embargo, en Docker hay matices.
\n\nError típico: pasar datos sensibles (claves API, contraseñas) mediante ENV en el Dockerfile. Esto deja los secretos en el historial de capas de la imagen.
Enfoque correcto:
\n- \n
- Use
--env-fileal iniciar el contenedor \n - Para producción, aplique Docker Secrets (en Swarm) o Kubernetes Secrets \n
- Para desarrollo local, use un archivo
.envcondocker-compose\n
Ejemplo de docker-compose.yml:
\nversion: '3.8'\nservices:\n app:\n build: .\n ports:\n - "3000:3000"\n env_file:\n - .env.production\n volumes:\n - ./logs:/app/logs\n\nEn el código TypeScript, use la librería dotenv para cargar las variables:
import dotenv from 'dotenv';\nimport { resolve } from 'path';\n\ndotenv.config({ path: resolve(__dirname, '../.env') });\n\nconst config = {\n port: parseInt(process.env.PORT || '3000', 10),\n dbUrl: process.env.DATABASE_URL,\n apiKey: process.env.API_KEY\n};\n\nexport default config;\n\nImportante: nunca haga commit de archivos .env reales