Docker y TypeScript: Contenerización de Aplicaciones de Principio a Fin
La contenerizació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 la máquina local del desarrollador hasta un servidor de producción. En este artículo, cubriremos el ciclo completo: desde un Dockerfile básico hasta una compilación de producción optimizada.
1. Conceptos Básicos de Docker para un Desarrollador 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 "funciona en mi máquina". 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 comenzar, 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
Comencemos 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('Hello from Docker + TypeScript!');});
app.listen(port, () => { console.log(`Server started on port ${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
# ---- Compilar 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. Permite:
- Instalar todas las dependencias (incluyendo devDependencies) solo durante la etapa de compilación
- Que solo el JavaScript compilado y las dependencias de producción lleguen a la imagen final
- Reducir el tamaño de la imagen entre 2 y 3 veces
Construye la imagen y ejecuta el contenedor:
docker build -t my-typescript-app .docker run -p 3000:3000 my-typescript-app3. 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, agreguemos un Dockerfile separado para desarrollo (puedes 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 los volumes, los cambios en el código se detectan inmediatamente (hot-reload), y se excluye /app/node_modules para evitar sobrescribir las dependencias del contenedor.