Modern .NET applications are increasingly deployed as containers, making Docker image optimization an essential part of application development. Large container images consume more storage, take longer to build and deploy, increase network transfer time, and often include unnecessary packages that expand the attack surface.

Fortunately, .NET provides several features to create smaller, faster, and more secure container images. Two of the most impactful techniques are multi-stage builds and chiseled images. Used together, they can significantly reduce image size while improving deployment efficiency and security.

In this article, you'll learn how these techniques work, when to use them, and the trade-offs involved when building production-ready .NET applications.

Why Docker Image Optimization Matters

A typical Docker image contains far more than just your application. It may include the .NET SDK, build tools, package managers, debugging utilities, and operating system components that aren't required at runtime.

Large images can lead to:

Optimizing your images helps deliver applications faster while reducing operational overhead.

Understanding Multi-Stage Builds

A common mistake is using the .NET SDK image for both building and running an application. While convenient, the SDK image contains compilers and development tools that are unnecessary in production.

A multi-stage build separates the build environment from the runtime environment.

Without Multi-Stage Builds

FROM mcr.microsoft.com/dotnet/sdk:8.0

WORKDIR /app

COPY . .

RUN dotnet publish -c Release -o /publish

ENTRYPOINT ["dotnet", "/publish/MyApp.dll"]

Although functional, the final image still contains the entire .NET SDK.

Using Multi-Stage Builds

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build

WORKDIR /src

COPY . .

RUN dotnet publish -c Release -o /app/publish

FROM mcr.microsoft.com/dotnet/aspnet:8.0

WORKDIR /app

COPY --from=build /app/publish .

ENTRYPOINT ["dotnet", "MyApp.dll"]

The first stage compiles the application, while the second stage copies only the published output into a lightweight runtime image.

This approach creates significantly smaller production images without changing application behavior.

What Are Chiseled Images?

Introduced for .NET container workloads, chiseled images are minimal runtime images that remove everything not required to run your application.

Unlike traditional runtime images, chiseled images exclude:

This results in:

A typical runtime image can be replaced with a chiseled image simply by changing the base image.

FROM mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled

WORKDIR /app

COPY --from=build /app/publish .

ENTRYPOINT ["dotnet", "MyApp.dll"]

For cloud-native applications running in Kubernetes or Azure Container Apps, chiseled images are often an excellent choice.

Multi-Stage Builds vs Chiseled Images

FeatureMulti-Stage BuildChiseled Image
Reduces image size
Removes SDK from production
Reduces operating system components
Improves security
Faster deployments
Can be used together

These techniques are complementary rather than competing approaches.

Additional Image Optimization Techniques

Use a .dockerignore File

Avoid copying unnecessary files into the Docker build context.

Example:

bin/
obj/
.git/
.vscode/
README.md

A smaller build context results in faster builds and better layer caching.

Optimize Docker Layer Caching

Docker caches layers independently. Place instructions that change less frequently near the top of the Dockerfile.

Instead of copying the entire project immediately:

COPY *.csproj .

RUN dotnet restore

COPY . .

RUN dotnet publish -c Release -o /app/publish

If only source files change, Docker can reuse the cached restore layer, reducing build time.

Publish in Release Mode

Always publish production applications using the Release configuration.

dotnet publish -c Release

Release builds include compiler optimizations that improve runtime performance and reduce unnecessary debugging information.

Consider Trimming for Smaller Applications

For applications that don't rely heavily on reflection, publishing with trimming can reduce deployment size.

dotnet publish -c Release -p:PublishTrimmed=true

However, trimming isn't suitable for every application. Frameworks that use reflection extensively may require additional configuration to prevent runtime issues. Always test thoroughly before enabling trimming in production.

When Native AOT Makes Sense

.NET also supports Native Ahead-of-Time (Native AOT) compilation.

Native AOT offers:

However, it comes with trade-offs, including limited support for certain reflection-based libraries and longer build times. It's best suited for APIs, microservices, and command-line tools where startup performance is critical.

Best Practices

Common Mistakes

Deploying SDK Images

SDK images are intended for development and build environments. Using them in production increases image size and exposes unnecessary tooling.

Copying the Entire Repository First

Copying all files before restoring dependencies invalidates Docker's cache, leading to slower builds.

Ignoring Base Image Updates

Outdated base images may contain known vulnerabilities. Regularly rebuild images using the latest supported .NET runtime images.

Assuming Smaller Always Means Better

Minimal images improve security and deployment speed, but they also remove tools commonly used for debugging. Ensure your operational workflow accounts for this limitation before adopting chiseled images.

Conclusion

Docker image optimization is more than reducing image size—it's about improving deployment speed, strengthening security, and creating efficient cloud-native applications.

Multi-stage builds eliminate unnecessary build tools from production images, while chiseled images remove non-essential operating system components to create minimal runtime environments. Together, they form a powerful combination for modern .NET deployments.

By following best practices such as using Release builds, optimizing Docker layer caching, maintaining a .dockerignore file, and selecting the appropriate runtime image, you can build containerized .NET applications that are faster to deploy, easier to maintain, and better suited for production environments. Thoughtful image optimization not only improves developer productivity but also contributes to more reliable and cost-effective deployments across any container platform.