Containerization has become the standard way to deploy modern ASP.NET Core applications. Smaller Docker images are faster to build, quicker to pull, consume less storage, and reduce deployment times across development, testing, and production environments.
Many .NET applications, however, ship with unnecessarily large Docker images due to inefficient Dockerfiles, unused dependencies, and poor layering strategies.
In this article, you'll learn practical techniques to reduce Docker image size for .NET 11 applications, improve build efficiency, and create production-ready container images.
Note: This article focuses on optimization techniques and build methodology. Actual image size reductions and startup improvements depend on your application, base image, dependencies, and deployment environment.
Why Docker Image Size Matters
Large Docker images introduce several operational challenges:
Longer CI/CD build times
Slower image downloads
Increased storage costs
Longer deployment times
Higher network bandwidth usage
Smaller images help applications scale more efficiently, especially in Kubernetes and cloud environments.
Understanding a Docker Image
A Docker image consists of multiple read-only layers.
Application
│
▼
Published Files
│
▼
.NET Runtime
│
▼
Linux Base Image
Each Docker instruction creates a new layer. Poor layer management increases image size unnecessarily.
Choose the Right Base Image
Microsoft provides multiple .NET container images.
| Image | Use Case |
|---|---|
| SDK | Building applications |
| ASP.NET Runtime | Running Web APIs |
| Runtime | Console applications |
| Runtime-Dependencies | Self-contained deployments |
A common mistake is deploying applications using the SDK image.
Avoid:
FROM mcr.microsoft.com/dotnet/sdk:11.0-preview
Instead, use the ASP.NET runtime image for production.
FROM mcr.microsoft.com/dotnet/aspnet:11.0-preview
The runtime image contains only the components needed to run the application.
Use Multi-Stage Builds
Multi-stage builds separate compilation from runtime.
FROM mcr.microsoft.com/dotnet/sdk:11.0-preview AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app
FROM mcr.microsoft.com/dotnet/aspnet:11.0-preview
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet","MyApi.dll"]
Why Multi-Stage Builds?
The SDK is only required during compilation.
By copying only the published output into the runtime image, unnecessary build tools and intermediate files are excluded.
Copy Only Required Files
Avoid copying the entire project immediately.
Instead:
COPY MyApi.csproj .
RUN dotnet restore
COPY . .
This improves Docker layer caching because dependency restoration is skipped when only source files change.
Use a .dockerignore File
Exclude unnecessary files from the build context.
Example:
bin/
obj/
.git/
.vscode/
TestResults/
README.md
A smaller build context reduces build time and avoids copying unnecessary content into the image.
Publish in Release Mode
Always publish production applications using Release mode.
dotnet publish -c Release
Release builds produce optimized binaries suitable for production deployments.
Enable Publish Trimming
Publish trimming removes unused framework code.
dotnet publish -c Release -p:PublishTrimmed=true
Before enabling trimming, verify that your application and third-party libraries are compatible, as aggressive trimming may remove code accessed through reflection.
Enable ReadyToRun Compilation
ReadyToRun precompiles assemblies to improve startup performance.
dotnet publish -c Release -p:PublishReadyToRun=true
This may increase the published output size while reducing application startup time. Evaluate the trade-off for your workload.
Use Invariant Globalization (When Appropriate)
Applications that do not require full globalization support can reduce image size.
<PropertyGroup>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
Only enable this option if your application does not depend on culture-specific formatting or localization.
Run as a Non-Root User
Production containers should avoid running as the root user.
RUN adduser --disabled-password appuser
USER appuser
Running as a non-root user improves container security without affecting image size significantly.
Optimize Docker Layers
Avoid multiple RUN instructions when possible.
Less efficient:
RUN apt-get update
RUN apt-get install curl
Better:
RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*
Combining related commands reduces unnecessary image layers and removes temporary package metadata.
End-to-End Dockerfile
A production-oriented example:
FROM mcr.microsoft.com/dotnet/sdk:11.0-preview AS build
WORKDIR /src
COPY MyApi.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app \
-p:PublishTrimmed=true
FROM mcr.microsoft.com/dotnet/aspnet:11.0-preview
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet","MyApi.dll"]
Join the conversation! Your thoughts help the community grow.