Introducción: Por qué la contenedorización avanzada de TypeScript es una necesidad
La contenedorización de aplicaciones TypeScript con Docker hace tiempo que dejó de ser solo una tendencia: es un estándar del desarrollo moderno. Sin embargo, los Dockerfile básicos que simplemente copian todo el proyecto y ejecutan npm start están lejos de ser ideales. A nivel avanzado, nos enfrentamos a tareas como optimizar el tamaño de las imágenes, garantizar la seguridad, organizar una arquitectura de microservicios y configurar la recuperación automática.
En este artículo, analizaremos técnicas que convertirán su pipeline de Docker para TypeScript en un estándar industrial: compilaciones de múltiples etapas (multi-stage builds), minimización de capas, uso de .dockerignore, Docker Compose para microservicios, healthchecks y trabajo con variables de entorno. Todos los ejemplos están escritos en TypeScript y listos para integrarse en proyectos reales.
1. Compilación de múltiples etapas: del desarrollo a producción
El principal problema de los Dockerfile básicos es que incluyen todo en la imagen final: fuentes, node_modules para desarrollo, herramientas de compilación. Para TypeScript esto es crítico, ya que tsc y devDependencies no son necesarios en producción.
La compilación de múltiples etapas (multi-stage build) resuelve este problema: la primera etapa compila y construye el TypeScript, la segunda crea una imagen mínima solo con los archivos necesarios.
Ejemplo de Dockerfile con dos etapas
# Etapa 1: Compilación de TypeScript
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY tsconfig.json ./
COPY src/ ./src/
RUN npm run build
# Etapa 2: Imagen de producción
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
Puntos clave:
- Usamos
npm ci— reproducción exacta de las dependencias desde el archivo lock. - Solo copiamos
dist/ynode_modules— los fuentes de TypeScript no llegan a producción. - Imagen Alpine — base mínima (alrededor de 5 MB).
Esta imagen pesa de 3 a 5 veces menos que una compilación ingenua. Para un proyecto grande con decenas de dependencias, la diferencia puede alcanzar cientos de megabytes.
Compilación de tres etapas con dependencias de desarrollo
A veces es necesario separar las dependencias de desarrollo y producción. Agregamos una tercera etapa para instalar solo las dependencias de producción:
# Etapa 1: Instalación de todas las dependencias
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
# Etapa 2: Compilación
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# Etapa 3: Producción
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=deps /app/node_modules ./node_modules
COPY package.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
2. Optimización de la imagen: .dockerignore, caché y capas
Cada instrucción COPY, RUN y ADD crea una nueva capa en Docker. Cuantas menos capas y más eficientemente se almacenen en caché, más rápida será la compilación.
.dockerignore: su primer aliado
Cree un archivo .dockerignore en la raíz del proyecto:
node_modules
dist
.git
.gitignore
*.md
.env
.env.*
Dockerfile
.dockerignore
coverage
.vscode
.idea
__tests__
Esto evita copiar basura al contexto de compilación y acelera la transferencia de archivos al demonio de Docker.
Orden correcto de las instrucciones
Docker almacena en caché las capas. Si primero copia package.json y luego ejecuta npm ci, esta capa se reutilizará hasta que cambie package.json. Luego copie los fuentes