A Practical Guide to .NET Container Images
When you pull a container image from mcr.microsoft.com/dotnet/*, you’re not just getting a runtime. You’re pulling from a thoughtfully layered collection of images, each designed to be secure, lightweight, and optimized for a specific purpose.
Grasping these layers makes it easier to troubleshoot issues, improve performance, enhance security, and choose the right image for your application.
Understanding .NET Image Families
.NET container images are grouped into families, each tailored for a specific role: building apps, running them, hosting web applications, or acting as a base for self-contained applications.
Each family is layered on top of the one beneath it, adding only what’s necessary. These layers directly influence image size and the libraries included by default.
Container Image Size vs Family
Breaking Down the Layers
1. Runtime-Deps Layer
This is the base layer: a minimal Linux image with just enough to run a native .NET binary.
Use cases:
Includes:
dotnet publish -c Release -r linux-x64 --self-contained true -o ./out
2. .NET Runtime Layer
Adds the .NET runtime so framework-dependent apps can run.
Ideal for:
Note: Does not include web-specific libraries or compilers.
3. ASP.NET Layer
Optimized for hosting ASP.NET Core applications.
Includes:
Best for:
4. SDK Layer
Contains everything needed for building and testing .NET apps, but shouldn’t be shipped to production.
Includes:
Recommendation: Use multi-stage Dockerfiles to produce lean production images.
dockerfile
# Example multi-stage Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app
FROM mcr.microsoft.com/dotnet/aspnet:8.0
COPY --from=build /app /app
Image Tag Anatomy
Each .NET container image tag encodes five key choices:
Understanding these components helps you make informed trade-offs for size, security, and compatibility rather than relying on defaults.
Image Variants
Variants modify base images to fit specific needs. They can:
These choices affect image size, performance, and security.
Example: “jammy” is an Ubuntu release codename used in some variants.
Composite Variant (-composite)
Composite images merge all shared-framework assemblies into a single precompiled binary blob, reducing JIT warm-up and speeding cold starts.
Pros:
Cons:
Build a composite image with:
dotnet publish /p:PublishReadyToRun=true
Distroless Images
Distroless images are minimalist, reducing attack surface and image size.
Characteristics:
Note: Alpine Linux uses musl instead of glibc. Many glibc binaries won’t run on Alpine without recompilation or a compatibility layer like gcompat.
Native AOT Images
Native AOT (Ahead-of-Time) images precompile apps into single native binaries, eliminating the need for CoreCLR or JIT.
Pros:
Build command:
dotnet publish -c Release -r linux-x64 /p:PublishAot=true
Usage:
Security Considerations
Every extra package increases potential vulnerabilities. Larger images often include shells, compilers, and debugging tools, which are convenient for development but risky in production.
Switching from a “fat” Debian-based ASP.NET image to a lean Alpine variant drastically reduces both package count and security exposure.
Conclusion
There’s no universal “best” .NET container image—only the one that fits your scenario.
Understanding official image layers helps you make intentional, informed choices for your app’s runtime environment. Pick images based on needs, not habits.