Docker Multistage-Builds
Qué cubre este artículo sobre Multistage-Builds
- Qué son los Multistage-Builds.
- Cómo separar herramientas de compilación de la imagen de ejecución.
- Ejemplos prácticos de Dockerfile.
- Ventajas para seguridad y tamaño de imagen.
- Consejos para imágenes en producción.
Introducción: Docker Multistage-Builds
Una imagen Docker debe ser lo más pequeña posible y libre de herramientas innecesarias. Si empaques todo en una sola imagen, arrastrarás compiladores, librerías de desarrollo y archivos de prueba. Los Multistage-Builds resuelven esto separando el proceso de compilación del entorno de ejecución real. Compilas la aplicación en una imagen que incluye todas las herramientas y luego copias solo los artefactos necesarios a una imagen de ejecución pequeña.
Este artículo explica cómo funcionan los Multistage-Builds y qué considerar al implementarlos.
Términos clave
- Multistage-Build: Dockerfile con múltiples instrucciones
FROM. - Build-Stage: Imagen donde se compila y prepara el código.
- Runtime-Stage: Imagen que finalmente se ejecuta.
- Distroless: Imagen de contenedor sin shell ni gestor de paquetes.
- Alpine: Imagen Linux pequeña.
- Scratch: Imagen base vacía.
- Artefacto: Resultado de una compilación.
- Layer: Capa de una imagen.
Cuándo tienen sentido los Multistage-Builds
- Las aplicaciones necesitan ser compiladas.
- Las dependencias de compilación no deben estar en la imagen final.
- Necesitas reducir el tamaño de la imagen.
- Quieres minimizar la superficie de ataque.
- Se requieren varios pasos, como pruebas y compilación.
Ejemplo simple: Python
# Build-Stage
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt
COPY . .
# Runtime-Stage
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]
Ejemplo simple: Go
# Build-Stage
FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o main .
# Runtime-Stage
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/main /main
CMD ["/main"]
Esta imagen no incluye shell ni gestor de paquetes.
Ejemplo simple: Node.js
# Build-Stage
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Runtime-Stage
FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
ENV NODE_ENV=production
RUN npm ci --only=production
CMD ["node", "dist/main.js"]
Alpine como runtime
Alpine es pequeño, pero basado en musl. Algunas aplicaciones necesitan librerías adicionales:
FROM alpine:latest
RUN apk add --no-cache libstdc++
COPY --from=builder /app/main /main
CMD ["/main"]
Imágenes Distroless
Las imágenes Distroless contienen solo librerías de ejecución, sin shell ni gestor de paquetes. Google proporciona imágenes prefabricadas:
gcr.io/distroless/static-debian12gcr.io/distroless/python3-debian12gcr.io/distroless/nodejs-debian12
Ventajas:
- Vector de ataque mínimo.
- Imágenes pequeñas.
- Sin shell interactivo.
Desventajas:
- No hay
docker execpara depuración. - Los archivos no se pueden instalar mediante
RUN.
Aprovechar el caché de compilación
Respeta el orden para maximizar el almacenamiento en caché:
- Copia archivos de dependencias.
- Instala dependencias.
- Copia el código fuente.
- Ejecuta la compilación.
Múltiples Build-Stages
FROM node:20 AS dependencies
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM node:20 AS build
WORKDIR /app
COPY --from=dependencies /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:20-slim AS runtime
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY package*.json ./
RUN npm ci --only=production
CMD ["node", "dist/main.js"]
Comparación de tamaños
| Enfoque | Tamaño de imagen |
|---|---|
| Imagen de compilación simple | 1 GB+ |
| Multistage con Slim | 200 MB |
| Multistage con Distroless | 20 MB |
Seguridad
- Sin herramientas de compilación en la imagen de ejecución.
- Sin compiladores ni archivos de encabezado.
- Sin dependencias de desarrollo.
- Sin directorios
.gitni de pruebas. - Usa usuarios sin privilegios de root.
- Sistema de archivos de solo lectura donde sea posible.
Errores comunes
- Rutas incorrectas: Nombre de stage en
--frommal escrito. - Dependencias faltantes: Librería de ejecución no incluida en la imagen de runtime.
- Shell faltante: Distroless hace imposible
docker exec. - Archivos olvidados: Assets necesarios no copiados.
- Caché de compilación: Copiar código fuente demasiado pronto, antes del paso de instalación.
- Multi-Arch: Sin especificar
BUILDPLATFORM.
Enlaces y más información
- BotServ.de Optimización de imágenes Docker
- BotServ.de Comandos Docker
- BotServ.de Seguridad Docker
- BotServ.de Docker Rootless
FAQ: Multistage-Builds
¿Necesito Multistage-Builds? Se recomiendan para lenguajes compilados y cuando el tamaño de imagen o la seguridad son importantes.
¿Son adecuadas las imágenes Distroless para homelabs? Sí, pero la depuración es más difícil.
¿Puedo usar más de dos stages? Sí, tantos como necesites.
¿Qué sucede con los stages intermedios? Están disponibles mientras no se ejecute limpieza o purga.
¿Son Alpine y Distroless iguales? No. Alpine es pequeño pero tiene shell y gestor de paquetes. Distroless es aún más minimalista.
Fuentes y lecturas adicionales
- Docker Multistage: https://docs.docker.com/build/building/multi-stage/
- Distroless: https://github.com/GoogleContainerTools/distroless
- Alpine: https://hub.docker.com/_/alpine
Resumen: Docker Multistage-Builds
Los Multistage-Builds separan el entorno de compilación del de ejecución y generan imágenes Docker más pequeñas y seguras. Para lenguajes compilados son prácticamente indispensables. Mediante imágenes de ejecución Distroless o Alpine puedes minimizar aún más la superficie de ataque. Lo importante es mantener rutas limpias, incluir todas las librerías de ejecución necesarias y verificar ocasionalmente el tamaño final de la imagen. Al implementar Multistage-Builds mejoras tanto los tiempos de compilación como la operación en producción.


