Kubernetes for .NET Developers — A Practical Guide cover image

Kubernetes for .NET Developers — A Practical Guide

By Julian Krause · Sunday, June 1, 2025 · ~3 min read

Kubernetes has become the default deployment target for microservices, yet many .NET developers interact with it only through YAML files they copy from Stack Overflow. This post covers the Kubernetes concepts that matter most for .NET service developers, with practical examples.

Health Checks Are Non-Negotiable

Kubernetes uses health probes to decide whether your container is alive, ready to receive traffic, and started up. .NET has first-class support for all three via the health checks middleware.

builder.Services.AddHealthChecks()
    .AddCheck("self", () => HealthCheckResult.Healthy())
    .AddSqlServer(connectionString, name: "database")
    .AddRedis(redisConnectionString, name: "cache");

app.MapHealthChecks("/healthz", new HealthCheckOptions
{
    Predicate = _ => true,
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});

app.MapHealthChecks("/ready", new HealthCheckOptions
{
    Predicate = check => check.Tags.Contains("ready")
});

app.MapHealthChecks("/alive", new HealthCheckOptions
{
    Predicate = _ => false // Just checks the app responds
});

The corresponding Kubernetes YAML maps these endpoints to probes:

livenessProbe:
  httpGet:
    path: /alive
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 10
readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5
startupProbe:
  httpGet:
    path: /healthz
    port: 8080
  failureThreshold: 30
  periodSeconds: 10

Graceful Shutdown and SIGTERM

When Kubernetes terminates a pod, it sends SIGTERM and waits for a grace period (default 30 seconds) before sending SIGKILL. Your .NET application must handle shutdown gracefully — finish in-flight requests, flush telemetry, close database connections.

var app = builder.Build();

var lifetime = app.Services.GetRequiredService<IHostApplicationLifetime>();

lifetime.ApplicationStopping.Register(() =>
{
    Log.Information("Shutting down — draining in-flight requests...");
    // Give load balancers time to remove this instance
    Thread.Sleep(TimeSpan.FromSeconds(5));
});

app.Run();

Resource Requests and Limits

Setting CPU and memory requests and limits correctly is critical. Too low, and your pods get OOM-killed under load. Too high, and you waste cluster resources. Start by profiling your application under realistic load and set requests to the P50 usage and limits to the P99.

resources:
  requests:
    memory: "256Mi"
    cpu: "250m"
  limits:
    memory: "512Mi"
    cpu: "1000m"

For .NET applications, remember that the garbage collector needs headroom. If your limit is 512Mi, the GC will start feeling pressure around 400Mi. The DOTNET_GCHeapHardLimit environment variable lets you tell the runtime about your memory constraints.

.NET Aspire Simplifies This

If you are using .NET Aspire, much of this configuration is handled for you. Aspire generates Kubernetes manifests, wires up health checks, and configures service discovery automatically. But understanding the underlying concepts is still essential — when something goes wrong in production, you need to know what Kubernetes is actually doing with your pods.

The investment in understanding Kubernetes pays dividends. Once your team is comfortable with the platform, deployments become routine, scaling is automatic, and your .NET services become genuinely cloud-native.


Comments (2)

Anna Friday, February 27, 2026 5:08 PM

Great post! I learned a lot from this.

Clara Monday, February 23, 2026 8:08 PM

Thanks for your comment — glad it helped!

Ben Saturday, February 28, 2026 5:08 PM

Interesting perspective, I hadn't thought of it that way.

Leave a Comment