Alpine-Like Container Security, Debian-Like Compatibility: Why I Picked Chiseled for .NET

Published: (February 12, 2026 at 11:31 AM EST)
4 min read
Source: Dev.to

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

VariantRuntime base image
Debianmcr.microsoft.com/dotnet/runtime:8.0
Alpinemcr.microsoft.com/dotnet/runtime:8.0-alpine
Ubuntu Chiseledmcr.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

VariantRuntime base imageMeasured sizeTrivy summary (measured)Native libuuid.so.1 call
Debianmcr.microsoft.com/dotnet/runtime:8.0213 MB87 CVEs (1 critical, 2 high, 24 medium, 60 low)✅ Works
Alpinemcr.microsoft.com/dotnet/runtime:8.0-alpine84 MB0 CVEs❌ Fails (DllNotFoundException)
Ubuntu Chiseledmcr.microsoft.com/dotnet/runtime:8.0-jammy-chiseled85.5 MB3 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

  1. Start with Ubuntu Chiseled for production services that need lean images and reliable native compatibility.
  2. Use Alpine only when musl compatibility has been proven for the entire dependency chain.
  3. Use Debian when broad compatibility or convenience outweighs the desire for a minimal footprint.
  4. 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:
0 views
Back to Blog

Related posts

Read more »

Cast Your Bread Upon the Waters

!Cover image for Cast Your Bread Upon the Watershttps://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-t...