Ir al contenido
angel.library

Multi-stage builds

Compilar una app (TypeScript → JS, un frontend con Vite, un binario de Go) necesita herramientas — compilador, devDependencies, a veces el código fuente completo. Pero para correr la app compilada, nada de eso hace falta. Si todo va en un solo FROM, la imagen final carga con herramientas de build que solo sirvieron una vez y después son peso muerto.

La solución: varias etapas, una sola imagen final

Sección titulada «La solución: varias etapas, una sola imagen final»
Dockerfile
# Etapa 1: build — tiene TypeScript, devDependencies, todo el código fuente
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# Etapa 2: runtime — solo lo necesario para correr
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
CMD ["node", "dist/server.js"]
  • AS build nombra la primera etapa.
  • COPY --from=build /app/dist ./dist copia solo el resultado compilado desde la etapa build hacia la etapa final — el compilador, el código TypeScript fuente y las devDependencies de la etapa build nunca llegan a la imagen final.
Sin multi-stageCon multi-stage
Tamaño de imagenGrande (incluye devDependencies, source, herramientas de build)Chica (solo runtime + artefactos)
Superficie de ataqueMayor (más paquetes = más CVEs potenciales)Menor
Velocidad de deploy/pullMás lenta (imagen más pesada)Más rápida
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
FROM deps AS build
COPY . .
RUN npm run build
FROM node:20-alpine AS runtime
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
CMD ["node", "dist/server.js"]

Cada etapa puede referenciar otra etapa anterior como base (FROM deps AS build) — reutiliza su cache igual que cualquier otra imagen.

  • Solo la última etapa del Dockerfile termina siendo la imagen final que docker build produce — las etapas intermedias existen solo durante el build (aunque quedan cacheadas para builds futuros).
  • Se puede construir una etapa específica sin llegar al final, útil para debug: docker build --target build -t mi-app-build ..