Construcción Multi-Etapa (Multi-Stage Builds) y BuildKit
Al desarrollar aplicaciones en lenguajes como Go, Rust, Java o frameworks frontend (React, Angular, Vue), nos encontramos con un dilema clásico:
- Para compilar el código necesitamos herramientas pesadas: SDKs, compiladores, paquetes de desarrollo y dependencias de testeo (ocupando entre y ).
- Para ejecutar la aplicación en producción solo necesitamos el binario compilado o los ficheros estáticos HTML/JS.
Las Construcciones Multi-Etapa (Multi-Stage Builds) resuelven este problema permitiendo declarar múltiples directivas FROM en un mismo Dockerfile y copiar exclusivamente los artefactos finales compilados a una imagen limpia y ultra-ligera.
1. Anatomía de una Multi-Stage Build #
1┌──────────────────────────────────────────────┐ 2│ ETAPA 1: BUILDER (Imagen Pesada ~1.2 GB) │ 3│ FROM node:20 AS builder │ 4│ - Instala dependencias completas de dev │ 5│ - Compila con Vite / TypeScript │ 6│ - Genera carpeta /app/dist │ 7└──────────────────────┬───────────────────────┘ 8 │ COPY --from=builder /app/dist /usr/share/nginx/html 9 ▼ 10┌──────────────────────────────────────────────┐ 11│ ETAPA 2: PRODUCCIÓN (Imagen Mínima ~25 MB) │ 12│ FROM nginx:alpine │ 13│ - Cero herramientas de compilación │ 14│ - Cero código fuente ni node_modules │ 15│ - Máxima seguridad y arranque instantáneo │ 16└──────────────────────────────────────────────┘
Ejemplo Práctico: Aplicación Frontend (React/Vue/Next estático) #
1# ─── Etapa 1: Compilación ─── 2FROM node:20-alpine AS builder 3WORKDIR /app 4 5COPY package*.json ./ 6RUN npm ci 7 8COPY . . 9RUN npm run build 10 11# ─── Etapa 2: Servidor de Producción ─── 12FROM nginx:alpine-slim 13WORKDIR /usr/share/nginx/html 14 15# Limpiar archivos por defecto de Nginx 16RUN rm -rf ./* 17 18# Copiar ÚNICAMENTE el resultado compilado desde el builder 19COPY /app/dist . 20 21# Configuración personalizada de Nginx 22COPY nginx.conf /etc/nginx/conf.d/default.conf 23 24EXPOSE 80 25CMD ["nginx", "-g", "daemon off;"]
2. Imágenes Base Ultraligeras: Alpine, Distroless y Scratch #
| Tipo de Imagen Base | Tamaño Típico | Shell y Herramientas | Caso de Uso Ideal |
|---|---|---|---|
| Alpine Linux | Sí (/bin/sh, gestor apk) | Entornos Node, Python o Ruby que requieren shell para scripting ligero. | |
| Google Distroless | No (sin shell, sin apt) | Runtimes de Java, Python o Node en entornos corporativos de alta seguridad. | |
| Scratch | No (completamente vacía) | Binarios estáticos compilados en Go o Rust (ej. CGO_ENABLED=0 go build). |
3. BuildKit y docker buildx #
BuildKit es el motor de compilación de última generación integrado en Docker que optimiza drásticamente las builds:
- Paralelización de Etapas: Si dos etapas no dependen entre sí, BuildKit las compila de forma concurrente.
- Caché de Paquetes (
--mount=type=cache): Conserva la caché denpm,pipomavenentre builds sin inflar la imagen final. - Compilación Multi-Arquitectura:
1# Habilitar BuildKit 2export DOCKER_BUILDKIT=1 3 4# Compilar para arquitecturas x86_64 y ARM64 (Apple Silicon / Raspberry Pi / Graviton) 5docker buildx build --platform linux/amd64,linux/arm64 -t miusuario/mi-app:v1.0 --push .
Resumen del tema
Conceptos clave #
- Multi-Stage Builds: técnica con múltiples
FROMque aísla las herramientas pesadas de compilación de la imagen final de producción. COPY --from=builder: transfiere únicamente binarios o carpetas compiladas entre etapas.- Distroless y Scratch: reducen herramientas y componentes; no impiden por sí solas ejecutar código malicioso.
- BuildKit: motor moderno que acelera compilaciones con ejecución paralela y caché inteligente.
Qué debes recordar #
Utiliza Multi-Stage Builds cuando permitan separar compilación y ejecución. El tamaño final depende de la aplicación y una imagen pequeña no garantiza ausencia de vulnerabilidades.
Ampliación: inspección y caché de imágenes #
Docker agrupa comandos por recurso: docker image rm nginx equivale a docker rmi nginx. docker history mi-app:v1 (o docker image history mi-app:v1) muestra el historial de instrucciones y tamaños; docker image inspect mi-app:v1 muestra su configuración y metadatos. docker image prune elimina imágenes dangling que no estén referenciadas por contenedores.
Para aprovechar la caché en Node.js, copia package.json y package-lock.json y ejecuta npm ci antes de copiar el resto del código. Así, editar el código no invalida la instalación de dependencias si sus manifiestos no cambian. BuildKit puede ejecutar en paralelo etapas independientes que sean necesarias para el resultado.
Alpine es una base mínima con shell; las variantes habituales de Distroless no incluyen shell ni gestor de paquetes; scratch es una base vacía para binarios que no necesitan otros archivos. Las variantes debug de Distroless sí pueden incluir herramientas. Una imagen pequeña reduce componentes, pero no garantiza que no existan vulnerabilidades ni impide ejecutar código malicioso.