.NET Container Images Explained: Chiselled, Alpine, and More
Microsoft publishes several variants of .NET container images, and choosing the right one has a meaningful impact on image size, security posture, and startup time. This article breaks down the options.
The Image Family
All official .NET images are published to the Microsoft Container Registry (MCR). There are four base image types:
| Image | Purpose | Includes |
|---|---|---|
sdk |
Building and publishing | .NET SDK, runtime, tools |
aspnet |
Running ASP.NET Core apps | ASP.NET Core + .NET runtime |
runtime |
Running console/.NET apps | .NET runtime only |
runtime-deps |
Running self-contained apps | Native OS dependencies only |
You should never ship an sdk image to production. Use the smallest image that your application actually needs.
Standard (Debian-Based) Images
The default images are based on Debian. They include a full Linux userland with a shell, package manager, and common utilities.
FROM mcr.microsoft.com/dotnet/aspnet:9.0
This pulls the Debian-based variant. It's the most compatible option and a safe default when you don't have specific size or security requirements. The ASP.NET runtime image is roughly 220 MB.
Alpine Images
Alpine Linux uses musl libc instead of glibc and strips out most utilities. The result is a significantly smaller image.
FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine
The Alpine ASP.NET image is around 110 MB — roughly half the Debian variant. For most ASP.NET Core applications, Alpine works without issues. However, some native libraries assume glibc, so test thoroughly if you depend on native interop.
When publishing a self-contained app for Alpine, you need the correct runtime identifier:
RUN dotnet publish -c Release -r linux-musl-x64 --self-contained -o /app/publish
Note the linux-musl-x64 RID — using linux-x64 will produce binaries linked against glibc, which will fail on Alpine.
Chiselled Images (Ubuntu-Based)
Chiselled images are Canonical's answer to minimal containers. They're based on Ubuntu but stripped down to only the files your application needs — no shell, no package manager, no /bin/bash.
FROM mcr.microsoft.com/dotnet/aspnet:9.0-noble-chiseled
Chiselled images are:
- Smaller than standard Debian images (comparable to Alpine)
- More secure because there's no shell for an attacker to exploit
- Non-root by default — they run as a non-root user out of the box
The trade-off is debuggability. You cannot docker exec into a chiselled container and poke around because there's no shell. For production, this is generally a benefit. For debugging, you'll need to use logging, tracing, or attach a debug sidecar.
Chiselled + AOT
For the absolute smallest images, combine Native AOT with chiselled runtime-deps:
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish src/MyApi/MyApi.csproj \
-c Release \
-r linux-x64 \
-p:PublishAot=true \
-o /app/publish
FROM mcr.microsoft.com/dotnet/runtime-deps:9.0-noble-chiseled
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["./MyApi"]
The resulting image can be as small as 30-50 MB. Native AOT compiles your application to a single native binary — no .NET runtime needed at runtime. The runtime-deps chiselled image provides only the minimal native libraries.
Not all .NET libraries support AOT, so check compatibility before adopting this approach.
Extra Variants
A few less common but useful variants:
-extra: Chiselled images with ICU and time zone data included (e.g.,9.0-noble-chiseled-extra). Use this if your app needs globalisation support.-composite: Images with ReadyToRun composite builds of the runtime, offering faster startup at the cost of slightly larger images.
Choosing the Right Image
Here's a practical decision tree:
- Do you need globalisation/ICU? Use
-extraor standard Debian/Alpine. - Do you need the smallest possible image? Use chiselled + AOT with
runtime-deps. - Do you need to exec into containers for debugging? Use standard Debian or Alpine.
- Are you running in production with no special requirements? Use chiselled.
- Default safe choice? Alpine is a good middle ground — small, secure, and debuggable.
Summary
The days of one-size-fits-all container images are gone. For .NET applications, start with Alpine or chiselled images and only move to the larger Debian variants if you have a specific compatibility need. The difference between a 220 MB image and a 40 MB image adds up across dozens of services and thousands of deployments.