Skip to content
BotServBotServ
DockerMultistageDockerfileOptimización de imágenes

Docker Multistage-Builds

Construir imágenes Docker más pequeñas y seguras con Multistage-Builds. Imágenes Build, Runtime y Distroless.

S

schutzgeist

4 min read
Docker Multistage-Builds

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-debian12
  • gcr.io/distroless/python3-debian12
  • gcr.io/distroless/nodejs-debian12

Ventajas:

  • Vector de ataque mínimo.
  • Imágenes pequeñas.
  • Sin shell interactivo.

Desventajas:

  • No hay docker exec para 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é:

  1. Copia archivos de dependencias.
  2. Instala dependencias.
  3. Copia el código fuente.
  4. 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

EnfoqueTamaño de imagen
Imagen de compilación simple1 GB+
Multistage con Slim200 MB
Multistage con Distroless20 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 .git ni 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 --from mal 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

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

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.

Volver al blog
Share:

Entradas relacionadas