Azure  

Benchmarking Azure Linux vs Debian for .NET Containers

The operating system inside a .NET container is easy to overlook.

Developers often focus on:

  • .NET version

  • Application code

  • Dockerfile

  • CPU and memory limits

  • Kubernetes configuration

But the container's base operating system also affects the final image, native dependencies, startup behavior, security surface, package availability, and operational workflow.

.NET 11 Preview 6 introduced official Azure Linux 4.0 container images, making Azure Linux an interesting option for teams evaluating their next .NET container platform. Microsoft also provides multiple .NET container variants designed for different deployment scenarios.

This raises a practical question:

How should developers compare Azure Linux and Debian for .NET container workloads?

The answer requires careful benchmarking.

There is also an important compatibility detail: current official .NET 11 Preview 6 container tags include Azure Linux 4.0, Ubuntu 26.04, and Alpine 3.24, but not Debian 12. Debian-based official .NET images exist for earlier supported releases such as .NET 8 and .NET 9.

Therefore, if the goal is specifically to benchmark .NET 11 on Azure Linux versus Debian, the Debian image must be constructed separately.

That distinction matters because benchmark methodology is only useful when the two environments are actually comparable.

Why the Container Base OS Matters

A .NET application does not run in isolation.

The runtime depends on operating-system components such as:

.NET Runtime
    |
    +-- Native Libraries
    |
    +-- libc
    |
    +-- OpenSSL
    |
    +-- ICU / Globalization
    |
    +-- Time Zone Data
    |
    +-- OS Kernel Interface

The container also inherits:

Base OS
Package Set
Security Updates
Native Dependencies
User Configuration
Filesystem Layout

The Linux distribution does not change the managed C# code, but it can affect the environment in which that code executes.

That is why a meaningful comparison should measure more than image size.

Azure Linux 4.0 and .NET 11

.NET 11 Preview 6 introduced Azure Linux 4.0 container images. Microsoft lists Azure Linux 4.0 images among the container-image improvements in Preview 6.

The current .NET container repository exposes .NET 11 Preview 6 Azure Linux 4.0 tags such as:

mcr.microsoft.com/dotnet/aspnet:11.0-preview-azurelinux4.0

and:

mcr.microsoft.com/dotnet/sdk:11.0-preview-azurelinux4.0

The exact tags should be pinned to a specific preview build when reproducing a benchmark. The official repository currently lists the Preview 6 Azure Linux 4.0 tags.

Azure Linux 4.0 itself is currently a preview release and Microsoft explicitly limits it to evaluation and testing rather than production use.

That means benchmark results using Azure Linux 4.0 should be presented as evaluation results, not as production recommendations.

What About Debian?

Debian has historically been an important base for .NET container images.

For example, official .NET 8 and .NET 9 container tags include Debian 12 Bookworm-based images such as:

mcr.microsoft.com/dotnet/aspnet:9.0-bookworm-slim

The official image documentation lists Debian 12 images for .NET 8 and .NET 9.

However, this changed with .NET 10.

Starting with .NET 10, default .NET container tags reference Ubuntu instead of Debian, and Debian-based images are no longer shipped as standard .NET 10 images.

This makes the original comparison more nuanced:

RuntimeOfficial Debian ImageAzure Linux Image
.NET 8YesYes
.NET 9YesYes
.NET 10No official Debian imageAzure Linux 3.0
.NET 11 Preview 6No official Debian imageAzure Linux 4.0

The benchmark can still be performed, but the methodology must clearly state how Debian was constructed.

Three Valid Benchmark Strategies

There are three reasonable approaches.

Strategy 1: Compare .NET 9 Official Images

Use:

.NET 9 + Debian 12
.NET 9 + Azure Linux 3.0

This provides official images on both sides.

Strategy 2: Compare .NET 11 Using a Custom Debian Image

Use:

.NET 11 Preview 6 + Azure Linux 4.0
.NET 11 Preview 6 + Custom Debian 12

This is closer to the original research question but requires building and maintaining the Debian runtime image yourself.

Strategy 3: Compare Base OS Independently

Benchmark:

Azure Linux 4.0
vs
Debian 12

while installing the same .NET runtime build into both environments.

This can provide a controlled OS comparison, but it no longer represents Microsoft's official container-image configurations.

For a C# Corner technical article, Strategy 2 is the most interesting, provided the custom Debian construction is documented clearly.

Build a Simple ASP.NET Core Application

Create a small API:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/api/health", () =>
{
    return Results.Ok(new
    {
        Status = "Healthy",
        Timestamp = DateTimeOffset.UtcNow
    });
});

app.MapGet("/api/compute", () =>
{
    long total = 0;

    for (int i = 0; i < 10_000_000; i++)
    {
        total += i;
    }

    return Results.Ok(total);
});

app.Run();

The two endpoints serve different purposes.

/api/health is useful for:

HTTP latency
Startup
Request overhead

/api/compute introduces CPU work.

This allows the benchmark to distinguish network behavior from CPU-intensive application behavior.

Create the Azure Linux Image

For .NET 11 Preview 6, use the official Azure Linux image:

FROM mcr.microsoft.com/dotnet/sdk:11.0-preview-azurelinux4.0 AS build

WORKDIR /src

COPY . .

RUN dotnet publish \
    -c Release \
    -o /app/publish \
    --no-restore

FROM mcr.microsoft.com/dotnet/aspnet:11.0-preview-azurelinux4.0

WORKDIR /app

COPY --from=build /app/publish .

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

For reproducible research, replace moving preview tags with exact immutable versions where the image repository provides them.

This prevents a later image rebuild from silently changing the benchmark environment.

Construct the Debian Environment Carefully

Because there is no official Debian 12 .NET 11 Preview 6 image in the current .NET 11 container tags, the Debian environment needs to be built explicitly.

A conceptual Dockerfile might begin with:

FROM debian:12-slim

WORKDIR /app

# Install the exact native dependencies
# required by the selected .NET runtime.

COPY published-app/ .

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

The exact dependency installation should be based on the .NET runtime build being tested.

Do not simply install a different .NET version into Debian and call the comparison a .NET 11 benchmark.

The following must remain equivalent:

.NET Runtime Version
Application Build
CPU Architecture
Application Configuration

Keep the Application Identical

The strongest benchmark changes one primary variable.

Use:

Same Application
Same .NET Version
Same CPU
Same Memory
Same Kernel Environment
Same Network
Same Workload
Different Container Base

The Dockerfiles can differ.

The application binaries should not.

Container Image Size

The first metric to record is image size.

For example:

docker images

Record:

Image Size
Compressed Registry Size
Number of Layers

A smaller image can reduce registry transfer and deployment time, but it does not automatically mean the application is faster.

This distinction is important.

Do not use:

Smaller image = faster application

as a conclusion.

Measure Startup Time

Container startup can be divided into multiple components:

Image Pull
   |
   v
Container Creation
   |
   v
.NET Process Startup
   |
   v
Application Initialization
   |
   v
First Successful Request

Do not combine all of these into one number without explaining what is being measured.

A useful experiment records:

Container Start
        |
        v
Application Ready
        |
        v
First Request

Run each scenario repeatedly.

Measure Warm Request Latency

After the application is running, send repeated requests:

curl http://localhost:8080/api/health

Measure:

p50
p95
p99

Do not report only the fastest response.

Warm-request latency helps determine whether the base OS materially affects steady-state request processing.

Measure CPU-Heavy Workloads

Use the compute endpoint:

curl http://localhost:8080/api/compute

The endpoint performs CPU work inside the managed application.

This is useful for comparing:

.NET Runtime
+
JIT
+
Native Runtime Dependencies
+
CPU Scheduling

However, the benchmark should not claim that a single synthetic loop represents all production workloads.

It represents one CPU-oriented workload.

Measure Memory Usage

Record:

Container Memory
Process Memory
Working Set / RSS

For example:

docker stats

can provide runtime container statistics.

Run the same workload against both images.

A useful result table is:

MetricAzure LinuxDebian
Idle memoryMeasureMeasure
Warm API memoryMeasureMeasure
Peak memoryMeasureMeasure
CPU workload memoryMeasureMeasure

Use measured values rather than assumptions.

Measure CPU Utilization

For the same workload, record:

Average CPU
Peak CPU
CPU per request

A container using fewer CPU resources at identical throughput may have an operational advantage.

But CPU measurements should always be collected under the same host conditions.

Control CPU and Memory Limits

Docker resource limits make comparisons more reproducible.

For example:

docker run \
  --cpus="2" \
  --memory="1g" \
  -p 8080:8080 \
  my-api

Use identical limits:

Azure Linux: 2 CPU / 1 GB
Debian:      2 CPU / 1 GB

Otherwise, the scheduler may give one benchmark more resources.

Benchmark Under Multiple CPU Limits

A stronger experiment uses several resource profiles:

1 CPU / 512 MB
2 CPU / 1 GB
4 CPU / 2 GB

This shows whether the behavior changes as the available resources increase.

The exact resource levels should reflect the deployment environment being evaluated.

Benchmark Concurrent Requests

A single request does not represent a production API.

Test multiple concurrency levels:

1
10
50
100
250

For each level, record:

Requests/sec
p50
p95
p99
Error Rate
CPU
Memory

Tools such as k6, wrk, or another controlled HTTP load generator can be used.

The same tool and configuration must be used for both containers.

Keep the Network Constant

The benchmark should use the same:

Host
Network
Port Mapping
Load Generator
Client Machine

for both tests.

Avoid comparing:

Azure Linux -> localhost

against:

Debian -> remote host

because the network path becomes a confounding variable.

Globalization Is an Important Difference

One area that deserves specific testing is globalization.

.NET container documentation notes that smaller container variants can omit globalization dependencies such as ICU and time-zone data, while Debian and Ubuntu images normally include these dependencies.

If your application uses:

CultureInfo
DateTime formatting
Currency formatting
String comparison
Localization

include globalization tests.

For example:

var culture =
    CultureInfo.GetCultureInfo("fr-FR");

var formatted =
    123456.78m.ToString("N2", culture);

The result should be functionally equivalent in both environments.

The benchmark should verify functionality before measuring performance.

Native Dependencies Matter

Some .NET applications rely on native libraries.

Examples include:

OpenSSL
ICU
Kerberos
LDAP
Graphics libraries
Database drivers

The .NET container documentation notes that some dependencies, such as Kerberos, LDAP, and MsQuic, are only required for specific scenarios.

Therefore, an application that uses only managed code may show different behavior from an application requiring native integrations.

A production benchmark should include the dependencies actually used by the application.

HTTPS Benchmark

If the production API uses HTTPS, benchmark HTTPS.

Do not conclude:

Azure Linux is faster

from an HTTP-only benchmark if production traffic uses TLS.

Use the same:

TLS Version
Certificate Configuration
Cipher Configuration
Client

where practical.

This is particularly important for APIs where TLS processing is a meaningful portion of request cost.

Test File IO

Containers can also differ in filesystem behavior depending on the workload and storage configuration.

Create a separate test for:

Sequential Read
Sequential Write
Small Files
Large Files
Temporary Files

Do not mix filesystem testing into the API latency benchmark.

The application should have clearly defined test cases.

Test Container Startup With Cached Images

Image-pull time and process-start time are different.

Measure both:

Cold Deployment

and:

Cached Image

A deployment platform may already have the image on the node.

Therefore:

Cold:
Pull + Start + Ready

Warm:
Start + Ready

These answer different operational questions.

Distroless Images Change the Comparison

The .NET ecosystem also provides distroless variants.

Microsoft describes distroless images as containing only the packages required for the application, without a package manager or shell, and notes that they can reduce attack surface and deployment size.

Azure Linux has .NET distroless variants, including:

azurelinux4.0-distroless

for .NET 11 Preview 6.

Do not mix:

Azure Linux distroless

with:

Debian full runtime

and call the result an OS comparison.

That becomes an image-variant comparison.

Security Surface

Container security should be part of the evaluation.

Record:

Package Count
Known Vulnerabilities
Root User
Shell Availability
Package Manager
Image Update Process

The official .NET container documentation notes that distroless images do not include a package manager or shell and run as a non-root user by default.

Use a vulnerability scanner such as the organization's approved container-scanning tool.

Do not compare CVE counts from different scanner databases or scan dates without documenting the methodology.

Run Vulnerability Scans at the Same Time

Base images change.

Microsoft's official .NET container repository states that images are rebuilt for base-image updates and that critical CVE fixes can trigger image rebuilds.

Therefore, record:

Image Digest
Scan Date
Scanner
Scanner Database Version
Severity Threshold

This makes the result reproducible.

Container Image Tags vs Digests

Avoid using only:

11.0-preview

in a research benchmark.

A tag can point to a different image later.

Use an immutable digest when possible:

image@sha256:...

Record the digest in the benchmark notes.

This is especially important for preview releases.

Test the Same Architecture

Do not compare:

Azure Linux amd64

against:

Debian arm64

unless architecture differences are specifically what you are studying.

Choose:

amd64

or:

arm64

and keep it identical.

Benchmark ARM Separately

If the production environment uses ARM-based infrastructure, run a separate benchmark.

The matrix becomes:

OSArchitecture
Azure Linuxamd64
Debianamd64
Azure Linuxarm64
Debianarm64

Do not mix these results.

CPU architecture can materially affect runtime performance.

Inspect the Published Image

Before benchmarking, record:

docker inspect IMAGE

and:

docker image inspect IMAGE

Capture:

Architecture
OS
Entrypoint
Environment
Layers
User
Exposed Ports

This can reveal differences that otherwise get overlooked.

.NET Container Port Configuration

Modern ASP.NET Core container images use port 8080 by default starting with .NET 8. The official .NET container documentation notes this behavior.

For example:

docker run \
  -p 8080:8080 \
  my-api

Make sure both containers expose and map the same application port.

Do not accidentally benchmark different networking configurations.

Use Multi-Stage Builds

A production-style Dockerfile should separate the SDK and runtime stages:

FROM mcr.microsoft.com/dotnet/sdk:11.0-preview-azurelinux4.0 AS build

WORKDIR /src

COPY . .

RUN dotnet restore

RUN dotnet publish \
    -c Release \
    -o /app/publish \
    --no-restore

FROM mcr.microsoft.com/dotnet/aspnet:11.0-preview-azurelinux4.0

WORKDIR /app

COPY --from=build /app/publish .

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

This prevents the complete SDK from being required in the final runtime image.

The same build structure should be used for the Debian experiment.

Do Not Benchmark Debug Builds

Use:

dotnet publish -c Release

for both images.

Avoid:

Debug
Debugger attached
Development logging
Hot reload
Development middleware

unless the goal is specifically to benchmark development workloads.

Keep Environment Variables Identical

Differences such as:

DOTNET_gcServer
DOTNET_TieredPGO
DOTNET_ReadyToRun
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT

can affect application behavior.

Do not change these settings between operating systems unless they are the variable being studied.

Benchmark Server GC Carefully

ASP.NET Core applications often benefit from server GC behavior, but the benchmark should use the same runtime configuration.

If one container has a different GC configuration, the comparison is no longer simply:

Azure Linux vs Debian

It becomes:

Azure Linux + Configuration A
vs
Debian + Configuration B

Document runtime settings explicitly.

Benchmark Native AOT Separately

Native AOT is another dimension.

Do not combine:

CoreCLR Azure Linux

with:

Native AOT Debian

because the runtime execution model has changed.

If Native AOT is important, create a separate experiment:

Azure Linux AOT
vs
Debian AOT

and document the supported toolchain and base image construction.

Measure Startup Correctly

For startup benchmarking, define the measurement point.

For example:

T0 = Container process starts

T1 = ASP.NET Core process starts

T2 = Application reports ready

T3 = First successful HTTP response

Then calculate:

Process Startup = T1 - T0

Application Ready = T2 - T0

First Request = T3 - T0

This is far more informative than saying:

Container started in 200 ms.

without explaining what "started" means.

Example Benchmark Matrix

A complete benchmark can use:

TestAzure LinuxDebian
Image sizeMeasureMeasure
StartupMeasureMeasure
First requestMeasureMeasure
Warm requestMeasureMeasure
CPU workloadMeasureMeasure
MemoryMeasureMeasure
10 concurrent requestsMeasureMeasure
100 concurrent requestsMeasureMeasure
HTTPSMeasureMeasure
File IOMeasureMeasure
GlobalizationVerifyVerify
Vulnerability scanRecordRecord

The table intentionally does not contain invented results.

The benchmark environment must generate them.

Benchmark Methodology

A repeatable test sequence can be:

  1. Build both container images.

  2. Record image digests.

  3. Record CPU architecture.

  4. Record host OS and kernel.

  5. Record Docker or container runtime version.

  6. Record .NET runtime version.

  7. Start the container with identical resource limits.

  8. Perform a warm-up phase.

  9. Run the startup test separately.

  10. Run steady-state API tests.

  11. Run concurrent load tests.

  12. Measure CPU and memory.

  13. Run globalization tests.

  14. Run file-IO tests where relevant.

  15. Scan both images using the same scanner.

  16. Repeat each benchmark multiple times.

  17. Calculate median and tail latency.

  18. Publish the environment with the results.

Avoid Benchmarking Your Laptop as Production

A developer laptop may have:

IDE
Browser
Docker Desktop
Background Services
CPU Throttling
Memory Pressure

running simultaneously.

For reliable research, use a dedicated or controlled host.

Document:

CPU Model
CPU Cores
RAM
Storage
OS
Kernel
Container Runtime
Network

Common Mistakes

Comparing Different .NET Versions

Do not compare:

.NET 11 + Azure Linux

against:

.NET 9 + Debian

and attribute the result to the operating system.

Assuming an Official Debian .NET 11 Image Exists

Current official .NET 11 Preview 6 tags do not list Debian.

Using Moving Tags

Preview tags can change.

Record image digests.

Comparing Different Image Variants

Do not compare a full runtime image with a distroless image unless image variant is the explicit subject.

Ignoring Native Dependencies

A benchmark that works for a pure managed API may not represent an application using native libraries.

Measuring Only Image Size

Image size is an operational metric, not application performance.

Measuring Only Average Latency

Always inspect tail latency.

Changing Resource Limits

Use identical CPU and memory limits.

Ignoring Architecture

amd64 and arm64 results should be evaluated separately.

Treating Azure Linux 4.0 as Production-Ready

Azure Linux 4.0 is currently preview and Microsoft explicitly limits it to evaluation and testing.

Troubleshooting

The Azure Linux Image Cannot Be Pulled

Verify the exact .NET 11 Preview tag.

The current official repository lists Preview 6 Azure Linux 4.0 tags.

Debian Container Cannot Start .NET

Check:

.NET Runtime Version
Native Dependencies
Architecture
libc Compatibility
OpenSSL
ICU
Timezone Data

Do not copy the dependency assumptions of an official image to a custom image without verifying them.

Application Behaves Differently

Compare:

Environment Variables
Culture
Timezone
Native Libraries
File Permissions
User
Working Directory

Performance Differs Significantly

First determine whether the difference is:

CPU
Memory
IO
Network
Native Library
GC
Startup

Then investigate that subsystem.

Do not immediately conclude that one Linux distribution is "faster."

Globalization Tests Fail

Check ICU and globalization configuration.

Microsoft's .NET container documentation notes that some optimized images omit globalization dependencies, while Debian and Ubuntu images include ICU and time-zone data.

Best Practices

  1. Keep the .NET version identical.

  2. Keep the application binary identical.

  3. Use the same CPU architecture.

  4. Use identical CPU and memory limits.

  5. Pin container image digests.

  6. Record the complete benchmark environment.

  7. Separate cold startup from warm request latency.

  8. Measure p50, p95, and p99 latency.

  9. Measure CPU and memory.

  10. Test multiple concurrency levels.

  11. Test HTTPS when production uses HTTPS.

  12. Test globalization when the application depends on it.

  13. Test native dependencies used by the application.

  14. Use the same load generator for both images.

  15. Run multiple iterations.

  16. Scan both images with the same security tooling.

  17. Document whether an image is official or custom-built.

  18. Do not compare different .NET runtime versions.

  19. Do not mix distroless and full images unless intentionally testing variants.

  20. Treat Azure Linux 4.0 results as preview evaluation results.

Frequently Asked Questions

Does .NET 11 have an official Debian container image?

The current .NET 11 Preview 6 official container tags do not list Debian. They include Ubuntu 26.04, Alpine 3.24, and Azure Linux 4.0.

Can I still benchmark .NET 11 on Debian?

Yes.

You can build a custom Debian 12 image containing the same .NET 11 runtime build, but you must document that it is a custom image rather than an official Microsoft .NET 11 Debian image.

Why did Debian disappear from the newer .NET container images?

Starting with .NET 10, the default .NET container tags moved from Debian to Ubuntu, and Debian-based images are no longer shipped for .NET 10. Microsoft cited the longer support lifecycle of Ubuntu as one reason for the change.

Is Azure Linux 4.0 production-ready?

Not currently.

Microsoft's Azure Linux documentation states that Azure Linux 4.0 is in preview and strictly limited to evaluation and testing.

Is Azure Linux faster than Debian?

There is no universal answer.

Performance depends on the application, runtime, native dependencies, hardware, resource limits, and workload.

The correct conclusion should come from a controlled benchmark.

Does a smaller container mean better performance?

Not automatically.

A smaller image can reduce image-transfer and storage costs, but steady-state application performance depends on the complete runtime environment.

Should I use Azure Linux for every .NET container?

No.

The choice should consider:

Application Compatibility
Support Requirements
Security
Operational Tooling
Base Image Lifecycle
Native Dependencies
Performance
Deployment Environment

Benchmarking is one input into that decision.

Can I use Debian with .NET 9?

Yes.

The official .NET container repository currently lists Debian 12 Bookworm images for .NET 9.

This can be a cleaner comparison if the goal is specifically to compare two officially supported .NET container bases rather than evaluate .NET 11 Preview 6.

Conclusion

Choosing a container base image is not simply a question of which Linux distribution is more popular.

For .NET applications, the base image influences:

Native Dependencies
+
Image Size
+
Startup Environment
+
Security Surface
+
Package Availability
+
Operational Workflow

.NET 11 Preview 6 makes the comparison particularly interesting because Microsoft now provides official Azure Linux 4.0 container images.

However, the comparison must be framed correctly.

There is currently no official Debian 12 .NET 11 Preview 6 image in the current .NET container tags. A Debian-based .NET 11 benchmark therefore requires a custom image.

That does not make the experiment invalid.

It makes the methodology more important.

A strong benchmark should look like:

Same .NET Version
       +
Same Application
       +
Same Architecture
       +
Same Resources
       +
Same Workload
       |
       +----------------+
       |                |
       v                v
Azure Linux         Debian
       |                |
       +-------+--------+
               |
               v
        Compare Results

Measure:

Image Size
Startup
First Request
Warm Latency
p95 / p99
Throughput
CPU
Memory
IO
Security
Compatibility

Do not publish a conclusion such as:

"Azure Linux is 20% faster than Debian."

unless the benchmark actually produces that result under a clearly documented and reproducible workload.

The more defensible conclusion is:

Container base images should be evaluated using the application's real workload, controlled infrastructure, identical .NET versions, and reproducible measurements rather than assumptions about the underlying Linux distribution.

For teams evaluating Azure Linux 4.0 specifically, the preview status should also be treated as a first-class consideration. The current Microsoft documentation limits Azure Linux 4.0 to evaluation and testing, so benchmark results should inform future architecture decisions rather than automatically becoming a production recommendation.