Introducción: ¿por qué contenerizar aplicaciones TypeScript?
El desarrollo de software moderno es impensable sin la contenerización. Docker se ha convertido en el estándar de facto para empaquetar, distribuir y ejecutar aplicaciones en entornos aislados. En combinación con TypeScript —un superconjunto de JavaScript con tipado estricto— Docker proporciona previsibilidad, portabilidad y simplifica los procesos de CI/CD.
En este artículo analizaremos cómo contenerizar correctamente una aplicación TypeScript: desde un Dockerfile básico hasta una compilación lista para producción utilizando docker-compose y compilación multi-etapa. Aprenderá cómo evitar errores típicos, reducir el tamaño de la imagen y acelerar el despliegue.
Fundamentos: TypeScript y Node.js en Docker
TypeScript es un lenguaje que se compila a JavaScript. Esto significa que para ejecutar una aplicación TypeScript en un contenedor, primero debemos compilar el código fuente y luego ejecutar el JavaScript resultante con Node.js. Docker es ideal para automatizar este proceso.
Creación de una aplicación TypeScript simple
Para empezar, crearemos una aplicación mínima en TypeScript. Estructura del proyecto:
my-ts-app/├── src/│ └── index.ts├── package.json├── tsconfig.json└── Dockerfilesrc/index.ts:
import express, { Request, Response } from 'express';
const app = express();const PORT = process.env.PORT || 3000;
app.get('/', (req: Request, res: Response) => { res.json({ message: 'Hello from TypeScript + Docker!' });});
app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`);});package.json:
{ "name": "my-ts-app", "version": "1.0.0", "scripts": { "build": "tsc", "start": "node dist/index.js", "dev": "ts-node src/index.ts" }, "dependencies": { "express": "^4.18.2" }, "devDependencies": { "@types/express": "^4.17.21", "@types/node": "^20.11.0", "typescript": "^5.3.3", "ts-node": "^10.9.2" }}tsconfig.json:
{ "compilerOptions": { "target": "ES2020", "module": "commonjs", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true }, "include": ["src/**/*"]}Dockerfile básico
La primera versión, la más simple del Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]Este Dockerfile copia todo el proyecto, instala las dependencias, compila TypeScript y ejecuta la aplicación. Sin embargo, este enfoque tiene desventajas: el tamaño de la imagen es grande y el almacenamiento en caché de las capas no es eficiente.
Optimización: compilación multi-etapa y reducción de tamaño
Para entornos de producción, se recomienda utilizar la compilación multi-etapa (multi-stage build). Esto permite separar las etapas de compilación y ejecución, dejando solo los archivos necesarios en la imagen final.
Dockerfile de producción con compilación multi-etapa
# Stage 1: BuildFROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./RUN npm ci --only=production
COPY . .RUN npm run build
# Stage 2: ProductionFROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./distCOPY --from=builder /app/node_modules ./node_modulesCOPY --from=builder /app/package.json ./
EXPOSE 3000
USER node
CMD ["node", "dist/index.js"]Ventajas de este enfoque:
- Menor tamaño de imagen — la capa final contiene solo el código compilado y las dependencias de producción.
- Seguridad — el código fuente de TypeScript y las dependencias de desarrollo no permanecen en el contenedor.