Alpine-Like Container Security, Debian-Like Compatibility: Why I Picked Chiseled for .NET
Source: Dev.to
Introduction
When building .NET services in containers the default advice is to pick the smallest image.
For production workloads that rule is incomplete. Runtime image selection is a trade‑off between native compatibility, security posture, and image footprint.
To illustrate this trade‑off I built a focused benchmark comparing Debian, Alpine, and Ubuntu Chiseled in a native‑library scenario. The benchmark source is available here: .
Official context on Chiseled containers for .NET can be found in Microsoft’s announcement: .
A managed‑only sample often hides real problems because many .NET services eventually rely on native Linux libraries (via P/Invoke or transitive dependencies).
The benchmark therefore uses a .NET 8 app that calls libuuid.so.1 from C#.
using System;
using System.Runtime.InteropServices;
class Program
{
[DllImport("libuuid.so.1")]
private static extern void uuid_generate(byte[] buffer);
static void Main()
{
Console.WriteLine("Generating UUID using native glibc library...");
try
{
var buffer = new byte[16];
uuid_generate(buffer);
Console.WriteLine("Success.");
}
catch (Exception ex)
{
Console.WriteLine("Exception:");
Console.WriteLine(ex);
}
}
}
Minimal project file
Exe
net8.0
Runtime bases compared
| Variant | Runtime base image |
|---|---|
| Debian | mcr.microsoft.com/dotnet/runtime:8.0 |
| Alpine | mcr.microsoft.com/dotnet/runtime:8.0-alpine |
| Ubuntu Chiseled | mcr.microsoft.com/dotnet/runtime:8.0-jammy-chiseled |
Dockerfiles
Debian
# ===== Build stage =====
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY NativeDemo.csproj .
RUN dotnet restore
COPY Program.cs .
RUN dotnet publish -c Release -o /app
# ===== Runtime stage =====
FROM mcr.microsoft.com/dotnet/runtime:8.0
RUN apt update && apt install -y libuuid1
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "NativeDemo.dll"]
Alpine
# ===== Build stage =====
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY NativeDemo.csproj .
RUN dotnet restore
COPY Program.cs .
RUN dotnet publish -c Release -o /app
# ===== Runtime stage =====
FROM mcr.microsoft.com/dotnet/runtime:8.0-alpine
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "NativeDemo.dll"]
Ubuntu Chiseled
# ===== Build stage =====
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY NativeDemo.csproj .
RUN dotnet restore
COPY Program.cs .
RUN dotnet publish -c Release -o /app
# ===== Extract libuuid from Ubuntu =====
FROM ubuntu:22.04 AS uuidstage
RUN apt update && apt install -y libuuid1
# ===== Runtime stage =====
FROM mcr.microsoft.com/dotnet/runtime:8.0-jammy-chiseled
WORKDIR /app
COPY --from=build /app .
COPY --from=uuidstage /usr/lib/x86_64-linux-gnu/libuuid.so.1 /usr/lib/
ENTRYPOINT ["dotnet", "NativeDemo.dll"]
Building and running
docker build -f Dockerfile.debian -t demo-debian .
docker run demo-debian
docker build -f Dockerfile.alpine -t demo-alpine .
docker run demo-alpine
docker build -f Dockerfile.chiseled -t demo-chiseled .
docker run demo-chiseled
Optional vulnerability scans
trivy image demo-debian
trivy image demo-alpine
trivy image demo-chiseled
The values shown below are taken from the repository’s benchmark measurement and may change as upstream base images are updated.
Benchmark results
| Variant | Runtime base image | Measured size | Trivy summary (measured) | Native libuuid.so.1 call |
|---|---|---|---|---|
| Debian | mcr.microsoft.com/dotnet/runtime:8.0 | 213 MB | 87 CVEs (1 critical, 2 high, 24 medium, 60 low) | ✅ Works |
| Alpine | mcr.microsoft.com/dotnet/runtime:8.0-alpine | 84 MB | 0 CVEs | ❌ Fails (DllNotFoundException) |
| Ubuntu Chiseled | mcr.microsoft.com/dotnet/runtime:8.0-jammy-chiseled | 85.5 MB | 3 CVEs (all low) | ✅ Works |
Analysis
- Alpine is the smallest image and reports no vulnerabilities, but it fails the glibc‑native call because it uses musl instead of glibc.
- Debian works out‑of‑the‑box, but its footprint is substantially larger and it carries a higher vulnerability count.
- Ubuntu Chiseled is nearly as small as Alpine while still passing the native call, and it reports only a few low‑severity CVEs.
The key takeaway is that measurable trade‑offs appear immediately once native dependencies are involved; no single distro is universally “best”.
Pragmatic policy for teams
- Start with Ubuntu Chiseled for production services that need lean images and reliable native compatibility.
- Use Alpine only when musl compatibility has been proven for the entire dependency chain.
- Use Debian when broad compatibility or convenience outweighs the desire for a minimal footprint.
- Always validate native behavior inside the exact runtime image you intend to ship.
This approach keeps the decision engineering‑driven and reproducible.
Sources
- Benchmark repository:
- Microsoft announcement: