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:
| Runtime | Official Debian Image | Azure Linux Image |
|---|
| .NET 8 | Yes | Yes |
| .NET 9 | Yes | Yes |
| .NET 10 | No official Debian image | Azure Linux 3.0 |
| .NET 11 Preview 6 | No official Debian image | Azure 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:
| Metric | Azure Linux | Debian |
|---|
| Idle memory | Measure | Measure |
| Warm API memory | Measure | Measure |
| Peak memory | Measure | Measure |
| CPU workload memory | Measure | Measure |
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:
| OS | Architecture |
|---|
| Azure Linux | amd64 |
| Debian | amd64 |
| Azure Linux | arm64 |
| Debian | arm64 |
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:
| Test | Azure Linux | Debian |
|---|
| Image size | Measure | Measure |
| Startup | Measure | Measure |
| First request | Measure | Measure |
| Warm request | Measure | Measure |
| CPU workload | Measure | Measure |
| Memory | Measure | Measure |
| 10 concurrent requests | Measure | Measure |
| 100 concurrent requests | Measure | Measure |
| HTTPS | Measure | Measure |
| File IO | Measure | Measure |
| Globalization | Verify | Verify |
| Vulnerability scan | Record | Record |
The table intentionally does not contain invented results.
The benchmark environment must generate them.
Benchmark Methodology
A repeatable test sequence can be:
Build both container images.
Record image digests.
Record CPU architecture.
Record host OS and kernel.
Record Docker or container runtime version.
Record .NET runtime version.
Start the container with identical resource limits.
Perform a warm-up phase.
Run the startup test separately.
Run steady-state API tests.
Run concurrent load tests.
Measure CPU and memory.
Run globalization tests.
Run file-IO tests where relevant.
Scan both images using the same scanner.
Repeat each benchmark multiple times.
Calculate median and tail latency.
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
Keep the .NET version identical.
Keep the application binary identical.
Use the same CPU architecture.
Use identical CPU and memory limits.
Pin container image digests.
Record the complete benchmark environment.
Separate cold startup from warm request latency.
Measure p50, p95, and p99 latency.
Measure CPU and memory.
Test multiple concurrency levels.
Test HTTPS when production uses HTTPS.
Test globalization when the application depends on it.
Test native dependencies used by the application.
Use the same load generator for both images.
Run multiple iterations.
Scan both images with the same security tooling.
Document whether an image is official or custom-built.
Do not compare different .NET runtime versions.
Do not mix distroless and full images unless intentionally testing variants.
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.