Introducción a Docker y TypeScript: por qué es necesaria la contenedorización
El desarrollo web moderno es impensable sin herramientas que garanticen la uniformidad del entorno. Docker se ha convertido en el estándar de facto para empaquetar, distribuir y ejecutar aplicaciones. En combinación con TypeScript, un lenguaje fuertemente tipado que se compila a JavaScript, la contenedorización proporciona una herramienta poderosa para crear servicios escalables y confiables.
¿Por qué contenedorizar una aplicación TypeScript? En primer lugar, resuelve el problema de «en mi máquina funciona». En segundo lugar, simplifica el despliegue en producción y en CI/CD. En tercer lugar, permite aislar las dependencias y las versiones de Node.js. En este artículo, analizaremos cómo crear una imagen Docker para una aplicación TypeScript, configurar una compilación multi-etapa y usar docker-compose para el desarrollo local.
Fundamentos de Docker para el desarrollador TypeScript
Antes de escribir código, recordemos los conceptos básicos de Docker: una imagen (image) es una plantilla para crear un contenedor; un contenedor (container) es una instancia en ejecución de una imagen; un Dockerfile es la instrucción para construir la imagen.
Para una aplicación TypeScript en Node.js, necesitaremos:
- El runtime de Node.js (generalmente la imagen
node:18-alpineonode:20-alpinepara un tamaño mínimo) - Instalación de dependencias (
npm installoyarn install) - Compilación de TypeScript a JavaScript (
tsc) - Ejecución de la aplicación compilada
Consideremos un ejemplo simple de una aplicación TypeScript (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 TypeScript + Docker!');});
app.listen(PORT, () => { console.log(`Server running on port ${PORT}`);});Y el tsconfig.json:
{ "compilerOptions": { "target": "ES2020", "module": "commonjs", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true }, "include": ["src"], "exclude": ["node_modules", "dist"]}Ahora crearemos un Dockerfile para esta aplicación.
Creación de un Dockerfile para una aplicación TypeScript
Un Dockerfile básico podría verse así:
FROM node:18-alpine AS builderWORKDIR /appCOPY package*.json ./RUN npm ciCOPY . .RUN npm run build
FROM node:18-alpine AS runnerWORKDIR /appCOPY --from=builder /app/dist ./distCOPY --from=builder /app/package*.json ./COPY --from=builder /app/node_modules ./node_modulesEXPOSE 3000CMD ["node", "dist/index.js"]Aquí se utiliza una compilación multi-etapa (multi-stage build). La primera etapa (builder) compila TypeScript a JavaScript, la segunda (runner) contiene solo el código compilado y las dependencias de runtime. Esto reduce el tamaño de la imagen final: los archivos fuente de TypeScript y las dependencias de desarrollo no se incluyen en ella.
Importante: ejecutamos npm ci (instalación limpia) en lugar de npm install para garantizar la reproducibilidad y la velocidad.
Optimización del Dockerfile: almacenamiento en caché de capas
Docker almacena en caché cada capa (instrucción). Para acelerar la compilación, copie los archivos que cambian con poca frecuencia primero. Por ejemplo, package.json y package-lock.json se copian antes que todos los archivos fuente, por lo que los cambios en el código no provocan una reinstalación de las dependencias.
Adicionalmente, se puede agregar un archivo .dockerignore:
node_modulesdist.git*.md.gitignore.envEsto evita que archivos innecesarios entren en el contexto de la compilación.
docker-compose para el desarrollo local
Para facilitar el desarrollo local, use docker-compose.yml. Esto permite ejecutar la aplicación con recarga automática (usando nodemon o