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:
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"]
This approach produces a cleaner runtime image by separating build and execution environments.
Layer Optimization Comparison
| Technique | Benefit |
|---|
| Multi-stage build | Removes SDK from runtime image |
| Runtime image | Smaller than SDK image |
| .dockerignore | Smaller build context |
| Release build | Optimized binaries |
| Publish trimming | Removes unused framework code |
| Layer optimization | Reduces duplicate image layers |
| ReadyToRun | Faster startup (larger binaries) |
| Non-root user | Improved container security |
Build Optimization Methodology
The research brief mentions startup benchmarks but does not include measured results or a benchmark environment. Instead of presenting unsupported numbers, use the following methodology to evaluate your own builds.
Test Environment
Keep the following consistent:
.NET SDK version
Docker version
Base image
Build configuration
Hardware
Operating system
Build Scenarios
Compare:
SDK image vs runtime image
Single-stage vs multi-stage build
Trimmed vs non-trimmed publish
ReadyToRun enabled vs disabled
Different base images
Metrics to Measure
Collect:
Final image size
Build duration
Container startup time
Memory usage
CPU utilization
Image pull time
Useful Tools
Useful tools include:
Validate optimizations under production-like conditions before adopting them across all services.
Best Practices
Use multi-stage Docker builds.
Prefer runtime images for production.
Exclude unnecessary files using .dockerignore.
Publish in Release mode.
Evaluate trimming before enabling it globally.
Run containers as non-root users.
Keep Dockerfiles simple and maintainable.
Regularly update base images with supported releases.
Common Mistakes
| Mistake | Impact |
|---|
| Using SDK image in production | Larger image size |
| Copying the entire project early | Poor layer caching |
| Missing .dockerignore | Larger build context |
| Keeping temporary package files | Increased image size |
| Running as root | Reduced security |
| Ignoring base image updates | Security and maintenance risks |
Troubleshooting
Docker Image Is Larger Than Expected
Review:
Use docker history to identify unexpectedly large layers.
Build Cache Is Not Working
Ensure project files are copied before the remaining source files so Docker can reuse the restore layer when dependencies have not changed.
Application Fails After Enabling Trimming
Some libraries rely on reflection or dynamically loaded types. Test the application thoroughly and disable trimming for incompatible assemblies if necessary.
FAQs
Why shouldn't I use the SDK image in production?
The SDK image contains build tools that are unnecessary for running an application, making it significantly larger than the runtime image.
What is a multi-stage build?
A Docker build process that separates compilation from runtime, allowing only the published application to be included in the final image.
Does trimming always reduce image size safely?
No. While trimming can significantly reduce the published output, some applications and libraries require additional configuration to work correctly after trimming.
Does ReadyToRun reduce image size?
No. ReadyToRun generally increases binary size in exchange for improved application startup performance.
How can I inspect Docker image layers?
Use commands such as docker history, docker inspect, and docker image ls to review image composition and identify optimization opportunities.
Conclusion
Optimizing Docker images is an important part of building efficient .NET applications for production. Smaller images improve build speed, reduce deployment time, lower storage costs, and simplify scaling across cloud and container orchestration platforms.
By using multi-stage builds, selecting appropriate base images, reducing build context, enabling Release optimizations, and validating changes through repeatable measurements, you can create production-ready Docker images that are both efficient and maintainable.