Tecnología que te entiende
Dockerfile avanzado: multi-stage builds e imágenes optimizadas
Docker

Dockerfile avanzado: multi-stage builds e imágenes optimizadas

05 May 2026 3 min de lectura 925 vistas

Un Dockerfile básico funciona — uno bien escrito produce imágenes más pequeñas, seguras y reproducibles. Las builds multi-stage son el avance más importante en optimización de imágenes Docker. Esta guía va de principiante a experto.

Por qué importa el tamaño de la imagen

Una imagen de 1 GB tarda mucho en subir al registry, en descargarse en producción y aumenta la superficie de ataque. El objetivo es incluir solo lo estrictamente necesario para correr la aplicación — sin compiladores, sin herramientas de debug, sin archivos temporales.

Imagen base Tamaño aprox. Cuándo usar
ubuntu ~78 MB Compatibilidad máxima, debugging
debian:slim ~75 MB Producción con apt disponible
alpine ~5 MB Tamaño mínimo (usa musl, no glibc)
scratch 0 MB Binarios estáticos Go/Rust

Multi-stage builds: el cambio de paradigma

Antes de multi-stage, tenías que elegir entre tener todo en una imagen enorme o mantener scripts externos de build complejos. Multi-stage permite múltiples etapas FROM en el mismo Dockerfile — cada una puede ser una imagen diferente — y solo copias lo que necesitas al resultado final.

Ejemplo: aplicación PHP con assets compilados

# Etapa 1: compilar assets de Node.js
FROM node:20-alpine AS assets
WORKDIR /build
COPY package*.json ./
RUN npm ci --only=production
COPY resources/ ./resources/
RUN npm run build

# Etapa 2: instalar dependencias PHP
FROM composer:2 AS composer
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-interaction

# Etapa 3: imagen final (solo lo necesario)
FROM php:8.3-fpm-alpine AS production

# Solo las extensiones PHP que necesitas
RUN docker-php-ext-install pdo pdo_mysql opcache

WORKDIR /var/www/html

# Copiar solo los artefactos compilados de las etapas anteriores
COPY --from=composer /app/vendor ./vendor
COPY --from=assets /build/public/build ./public/build
COPY . .

# No root
RUN adduser -D -u 1000 appuser && chown -R appuser:appuser /var/www/html
USER appuser

EXPOSE 9000
CMD ["php-fpm"]
💡 El resultado: La imagen final no tiene Node.js, npm, Composer ni las herramientas de compilación. Solo PHP-FPM + extensiones + tu código. De ~800 MB a ~120 MB.

Buenas prácticas de Dockerfile

Ordenar instrucciones de menos a más cambiantes

# MAL: si cambias el código, Docker reconstruye desde la instalación de dependencias
FROM node:20-alpine
COPY . /app
RUN npm install

# BIEN: las dependencias se cachean, solo el código se vuelve a copiar
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci           # ← cacheado mientras package.json no cambie
COPY . .             # ← solo esto se vuelve a copiar en cada build

Un proceso por contenedor

# Cada CMD ejecuta un solo proceso principal — Docker lo monitorea
CMD ["php-fpm", "-F"]

# Si necesitas múltiples procesos, usa un process manager
# o divide en múltiples contenedores coordinados por Compose

.dockerignore: excluir lo innecesario

# .dockerignore
node_modules/
.git/
*.log
.env
storage/logs/*
tests/
*.md

Variables de build con ARG y ENV

# ARG: solo disponible durante el build
ARG APP_VERSION=1.0.0
LABEL version="${APP_VERSION}"

# ENV: disponible también en el contenedor en ejecución
ENV APP_ENV=production \
    PHP_OPCACHE_ENABLE=1 \
    PHP_MEMORY_LIMIT=256M

Verificar el resultado

# Ver tamaño de todas las imágenes
docker images

# Inspeccionar las capas y su tamaño
docker history mi-app:latest

# Análisis profundo con dive (instalar por separado)
docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock \
    wagoodman/dive mi-app:latest
✅ Checklist de imagen optimizada: imagen base slim/alpine ✓ · multi-stage para separar build de runtime ✓ · .dockerignore configurado ✓ · usuario no-root ✓ · capas ordenadas para máximo caché ✓ · solo las dependencias de producción ✓
"Una imagen Docker es tan pequeña como lo que decides no incluir en ella."