Si utilizas Docker para replicar tus aplicaciones de .NET Core seguramente te has preguntado lo siguiente ¿Cómo puedo optimizar el tamaño de mis imágenes? Yo por lo personal e entrado en este debate múltiples ocasiones, tras investigar múltiples fuentes en internet y probar varias opciones hoy traigo la opción que mejor me a funcionado para mis proyectos de .NET y su replicación con Docker. A continuación, veremos el proceso para poder optimizar lo mas posible una imagen de Docker creada dentro de un proyecto de .NET Core. Abajo tenemos una imagen por defecto de Docker generada por Visual Studio, esto nos servirá de referencia para ver todas las modificaciones y optimizaciones que le realizaremos.
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /source
# copy csproj and restore as distinct layers
COPY *.sln .
COPY aspnetapp/*.csproj ./aspnetapp/
RUN dotnet restore
# copy everything else and build app
COPY aspnetapp/. ./aspnetapp/
WORKDIR /source/aspnetapp
RUN dotnet publish -c release -o /app --no-restore
# final stage/image
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
COPY --from=build /app ./
ENTRYPOINT ["dotnet", "aspnetapp.dll"]
Utilizar una imagen base más liviana
Por defecto construimos nuestra imagen de .NET Core sobre Linux, pero muchas veces no nos preguntamos que otras opciones tenemos, es allí donde tras investigar varios blogs y sitios descubrí que existe una imagen de .NET para Alpine. Alpine es mucho mas liviano que Ubuntu y nos dejara generar imágenes mucho más livianas de .NET Core desde un inicio. Abajo encontraremos como cargar la imagen de alpine a nuestro Docker File.
# Cambiar nuestra imagen para el build
FROM mcr.microsoft.com/dotnet/sdk:6.0-alpine AS build
# cambiar nuestra imagen para publish
FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine
# Vamos a espicificar nuestro tarhet a Apline
RUN dotnet restore --runtime alpine-x64
Construcción en varias etapas
Por defecto construimos nuestra imagen de .NET Core sobre Linux, pero muchas veces no nos preguntamos que otras opciones tenemos, es allí donde tras investigar varios blogs y sitios descubrí que existe una imagen de .NET para Alpine. Alpine es mucho mas liviano que Ubuntu y nos dejara generar imágenes mucho más livianas de .NET Core desde un inicio. Abajo encontraremos como cargar la imagen de alpine a nuestro Docker File. En el siguiente enlace podremos aprender más acerca de la Construcción en varias etapas.
Recortar el Assembly
Dentro de .NET tenemos a nuestra disposición herramientas que nos dejan recortar nuestras dependencias, de esta manera optimizando aun mas nuestra imagen de Docker. En el siguiente enlace podremos leer la documentacion oficial de Microsoft acerca de este tema. Tengo que mencionar que esta opción únicamente esta disponible en las publicaciones “self-contanined” que es cuando el .NET Core Runtime y tu aplicación son publicados en conjunto y no de manera separada. A continuación, iremos paso a paso con los cambios que debemos realizar en nuestro archivo de Docker para poder hacer uso de estas ventajas y optimizar aún más nuestra aplicación.
Primero para proceder a publicar nuestra aplicación en su modalidad “self-contained” debemos agregar a nuestra etapa de publicación que será publicada en una versión de Alpine.
# Habilitar el self-contained
RUN dotnet publish -c release -o /app \
--no-restore \
--runtime alpine-x64
Tras haber especificado esto habilitaremos el modo “self-contained” y habilitaremos el recorte de nuestros Assemblies dentro de nuestra configuración de la publicación.
# Habilitar el self-contained
RUN dotnet publish -c release -o /app \
--no-restore \
--runtime alpine-x64 \
--self-contained true \
/p:PublishTrimmed=true
Lo ultimo que debemos de realizar es cambiar nuestro punto de entrada de la aplicación, recordemos que ahora nuestra aplicación esta corriendo junto el Runtime por lo que no debemos de especificar el .dll en nuestro punto de entrada, únicamente apuntaremos a nuestra publicación de la siguiente manera.
# Cambiar nuestro Entry Point
ENTRYPOINT ["./aspnetapp"]
Pasos adicionales
Tras probar varias recetas para optimizar las imágenes de Docker, quiero compartir la configuración que mejor me a funcionando, así quedaría la configuración de Docker con las varias optimizaciones realizadas y algunos pasos adicionales que he encontrado buscando varias técnicas de optimización no solo de las imágenes si no también del proceso de creación de estas.
FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine AS base
WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:6.0-alpine AS publish
WORKDIR /src
COPY *.sln .
COPY aspnetapp/*.csproj ./aspnetapp/
RUN dotnet restore --runtime alpine-x64
COPY ..
RUN dotnet publish -c release -o /app \
--no-restore \
--runtime alpine-x64 \
--self-contained true \
/p:PublishTrimmed=true \
/p:IncludeAllContentForSelfExtract=true \
/p:PublishSingleFile=true
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["aspnetapp"]
Conclusión
En conclusión, con tan solo utilizar la imagen adecuada y seguir las recomendaciones para crear imágenes de Docker podemos optimizar de manera considerable nuestras imágenes de Docker, no esta de mas agregar que cada etapa de la optimización debe de ser probada dentro de tu aplicación, por ejemplo, si utilizas alguna librería puede que requieras instalar librerías adicionales en tu imagen para que esta funcione con normalidad.