Docker y TypeScript: contenedorización de aplicaciones de la A a la Z
La contenedorización se ha convertido en el estándar del desarrollo moderno. Docker permite empaquetar una aplicación TypeScript junto con todas sus dependencias y entorno, garantizando su funcionamiento estable en cualquier plataforma, desde el ordenador local del desarrollador hasta un servidor de producción. En este artículo, analizaremos el ciclo completo: desde un Dockerfile básico hasta una compilación de producción optimizada.
1. Fundamentos de Docker para desarrolladores de TypeScript
Docker es una plataforma para crear, distribuir y ejecutar aplicaciones en entornos aislados (contenedores). Para aplicaciones TypeScript que se compilan a JavaScript y se ejecutan en Node.js, Docker resuelve tres tareas clave:
- Reproducibilidad del entorno: olvídate de la frase "en mi máquina funciona". Las versiones de Node.js, npm y paquetes del sistema quedan fijadas en la imagen.
- Aislamiento: cada aplicación se ejecuta en su propio contenedor sin afectar a otros servicios.
- Escalabilidad: los contenedores se replican y orquestan fácilmente.
Antes de empezar, asegúrate de tener Docker y Docker Compose instalados. Puedes verificarlo con los comandos:
docker --versiondocker-compose --version2. Creación de un Dockerfile para una aplicación TypeScript
Empecemos con una aplicación TypeScript simple. Supongamos que tenemos un archivo src/index.ts:
import express from 'express';
const app = express();const port = process.env.PORT || 3000;
app.get('/', (req, res) => { res.send('¡Hola desde Docker + TypeScript!');});
app.listen(port, () => { console.log(`Servidor iniciado en el puerto ${port}`);});Ahora creemos un Dockerfile en la raíz del proyecto:
# ---- Imagen base para desarrollo ----FROM node:18-alpine AS baseWORKDIR /appCOPY package*.json ./RUN npm ci --only=production
# ---- Compilación de TypeScript ----FROM base AS buildCOPY tsconfig.json ./COPY src/ ./src/RUN npm ci && npm run build
# ---- Imagen de producción ----FROM node:18-alpine AS productionWORKDIR /appCOPY --from=build /app/dist ./distCOPY --from=base /app/node_modules ./node_modulesEXPOSE 3000CMD ["node", "dist/index.js"]Esto es una compilación multi-etapa (multi-stage build). Permite:
- Instalar todas las dependencias (incluyendo devDependencies) solo en la etapa de compilación
- Que en la imagen final solo se incluya el JavaScript compilado y las dependencias de producción
- Reducir el tamaño de la imagen entre 2 y 3 veces
Construye la imagen y ejecuta el contenedor:
docker build -t mi-app-typescript .docker run -p 3000:3000 mi-app-typescript3. Configuración de docker-compose para desarrollo y producción
Docker Compose simplifica el trabajo con aplicaciones multi-contenedor. Creemos un docker-compose.yml:
version: '3.8'
services: app: build: context: . target: development ports: - "3000:3000" volumes: - .:/app - /app/node_modules environment: - NODE_ENV=development command: npm run dev
# Versión de producción app-prod: build: context: . target: production ports: - "80:3000" environment: - NODE_ENV=productionPara mayor comodidad, añadamos un Dockerfile separado para desarrollo (se puede extender la imagen base):
# Dockerfile.devFROM node:18-alpineWORKDIR /appCOPY package*.json ./RUN npm installCOPY . .EXPOSE 3000CMD ["npm", "run", "dev"]Ejecución en modo desarrollo:
docker-compose up appGracias a volumes, los cambios en el código se detectan automáticamente (hot-reload), y /app/node_modules está excluido para no sobrescribir las dependencias del contenedor.