Optimizar el tamaño de una imagen en Docker es una de las prácticas más valiosas cuando trabajas con proyectos reales, y la técnica de multi-stage en Dockerfile te permite lograrlo separando la compilación de la imagen final. Aprenderás cómo funciona esta estrategia con un proyecto de ASP.NET, por qué reduce el peso de tu contenedor y cuándo conviene aplicarla.
¿Por qué un Dockerfile sencillo deja de ser suficiente?
Cuando empiezas con Docker, lo natural es buscar archivos cortos y limpios. Dos o tres líneas alcanzan para una app básica de Python: defines la imagen base, creas un directorio de trabajo, instalas dependencias y ejecutas. Y listo.
Pero el escenario cambia cuando el proyecto crece. Una API en ASP.NET, por ejemplo, requiere compilación, publicación y empaquetado. Ahí es donde el Dockerfile que genera Visual Studio por defecto sorprende: 25 líneas y múltiples sentencias from repetidas [3:00].
¿Qué es una imagen base en Docker? Es la imagen sobre la que se construye tu contenedor. Se declara con la sentencia from y define el sistema y las herramientas iniciales disponibles.
Esa repetición de from no es un error. Es la pista de que el archivo está usando multi-stage builds.
¿Qué es la técnica multi-stage en Docker y cómo funciona?
La traducción literal sería etapas múltiples, y describe bien lo que ocurre: el Dockerfile crea varias imágenes intermedias y solo conserva lo necesario en la final. En el ejemplo de Visual Studio para ASP.NET, las etapas son tres.
¿Cuáles son las tres etapas del Dockerfile generado por Visual Studio?
- Imagen base con runtime. Queda en espera, sin ejecutar nada todavía.
- Imagen de build con SDK. Toma el código fuente, los archivos del proyecto de C# y ejecuta
dotnet build para compilar.
- Imagen de publicación. Ejecuta
dotnet publish y deja únicamente los ensamblados listos para ejecutarse.
Al final, esos ensamblados se copian a la imagen base original. El resultado es una imagen final ligera, sin código fuente ni herramientas de compilación, solo con lo estrictamente necesario para correr la aplicación [5:30].
¿Cómo se simplifica este flujo según la documentación de Microsoft?
La documentación oficial propone una versión más compacta con dos etapas en lugar de tres:
- Una primera etapa que restaura y publica la aplicación.
- Una segunda etapa que monta esa publicación sobre la imagen de ASP.NET.
Menos líneas, misma lógica y un Dockerfile mucho más legible.
¿Cuándo conviene usar multi-stage builds? Cuando tu proyecto requiere compilación previa (como .NET, Java o Go) y quieres que la imagen final no incluya el SDK ni el código fuente, solo los binarios listos para ejecutar.
¿Cómo construir una imagen multi-stage de ASP.NET paso a paso?
La prueba se hace en VS Code, aunque gracias a Docker no necesitas tener .NET instalado para reproducirla. Estos son los pasos clave del flujo demostrado:
- Crear un proyecto Web API con el comando
dotnet new llamándolo Multistage.
- Añadir un archivo
Dockerfile en la misma carpeta del proyecto.
- Copiar el contenido del Dockerfile que aparece en la documentación de Microsoft.
- Ajustar el nombre del proyecto y de la DLL para que coincidan con Multistage.
- Ejecutar
docker build -t multistage . desde la terminal.
Durante la compilación, Docker muestra mensajes como stage 1 of 3, 2 of 3 y 3 of 3, confirmando que cada etapa se ejecuta en orden [9:10]. Esa es la señal visible de que el multi-stage está funcionando.
¿Cuánto pesa la imagen final y qué viene después?
Una vez terminada la compilación, la imagen aparece en Docker Desktop. El peso final del ejemplo es 843 MB para una aplicación base de .NET sin modificaciones [10:45]. Es mucho menos de lo que pesaría arrastrando SDK, fuentes y artefactos intermedios, pero todavía hay margen.
Entre las herramientas y conceptos que conviene tener claros para seguir optimizando:
- Dockerfile: archivo de instrucciones que define cómo se construye una imagen.
- from: sentencia que declara la imagen base de cada etapa.
- SDK vs runtime: el SDK contiene herramientas para compilar; el runtime solo lo necesario para ejecutar. La imagen final debe basarse en runtime.
- dotnet build y dotnet publish: comandos que compilan y empaquetan la aplicación antes de copiarla a la imagen final.
- docker build -t: comando que crea la imagen y le asigna una etiqueta para identificarla.
La base ya está lista. ¿Qué optimizaciones aplicarías tú primero para bajar esos 843 MB? Cuéntame en los comentarios.