- Authors

- Name
- Phillip Pham
- @ddppham
TL;DR
.NET-8-Anwendungen lassen sich mit einem Multi-Stage Dockerfile schlank containerisieren. Nutze die Health-Check-Middleware von ASP.NET Core für Liveness- und Readiness-Probes. Konfiguriere Kestrel explizit für Container-Umgebungen und setze immer Resource Limits in deinen Kubernetes-Deployments.
.NET auf Kubernetes — vom Code zum laufenden Pod
ASP.NET Core ist für Container gebaut. Das Framework läuft cross-platform, bringt einen eigenen Webserver (Kestrel) mit und unterstützt Health Checks nativ. Trotzdem gibt es bei der Containerisierung einige Stolperfallen.
Multi-Stage Dockerfile
Ein Multi-Stage Build trennt Build-Umgebung und Runtime-Image. Das Ergebnis: ein schlankes Produktions-Image ohne SDK und Build-Artefakte.
# Stage 1: Build
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# Dependencies zuerst kopieren (besseres Layer-Caching)
COPY *.csproj ./
RUN dotnet restore
# Dann den Rest
COPY . .
RUN dotnet publish -c Release -o /app/publish --no-restore
# Stage 2: Runtime
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
# Non-Root User verwenden
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
USER appuser
COPY /app/publish .
ENV ASPNETCORE_URLS=http://+:8080
ENV DOTNET_EnableDiagnostics=0
EXPOSE 8080
ENTRYPOINT ["dotnet", "MeineApp.dll"]
Dieses Dockerfile erzeugt ein Image von ca. 220 MB statt über 1 GB mit dem vollen SDK. Der Non-Root-User ist wichtig für Kubernetes-Cluster mit Pod Security Standards.
Bauen und testen:
# Image bauen
docker build -t meine-dotnet-app:v1.0 .
# Lokal testen
docker run -p 8080:8080 meine-dotnet-app:v1.0
# Image-Größe prüfen
docker images meine-dotnet-app
Health Checks mit ASP.NET Core
Kubernetes braucht Health-Check-Endpunkte, um zu entscheiden, ob ein Pod Traffic empfangen soll (Readiness) und ob er neu gestartet werden muss (Liveness). ASP.NET Core bringt dafür eine Middleware mit.
In Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Health Checks registrieren
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy())
.AddNpgSql(builder.Configuration.GetConnectionString("Default")!,
name: "database",
tags: new[] { "ready" })
.AddRedis(builder.Configuration.GetConnectionString("Redis")!,
name: "redis",
tags: new[] { "ready" });
var app = builder.Build();
// Liveness: Nur Grundfunktion prüfen
app.MapHealthChecks("/healthz", new HealthCheckOptions
{
Predicate = check => !check.Tags.Contains("ready")
});
// Readiness: Alle Abhängigkeiten prüfen
app.MapHealthChecks("/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});
app.MapControllers();
app.Run();
Der /healthz-Endpunkt prüft nur, ob die App selbst läuft. Der /ready-Endpunkt prüft zusätzlich Datenbank und Redis — so bekommt der Pod erst Traffic, wenn alle Abhängigkeiten verfügbar sind.
Kestrel für Container konfigurieren
Kestrel ist der Standard-Webserver in ASP.NET Core. In Containern musst du einige Defaults anpassen.
{
"Kestrel": {
"Limits": {
"MaxConcurrentConnections": 200,
"MaxRequestBodySize": 10485760,
"KeepAliveTimeout": "00:02:00",
"RequestHeadersTimeout": "00:00:30"
}
},
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft.AspNetCore": "Warning"
}
}
}
Drei wichtige Punkte:
- Port: Setze
ASPNETCORE_URLS=http://+:8080als Umgebungsvariable. Binde niemals auflocalhost, sonst ist der Container von außen nicht erreichbar. - Logging: Reduziere das Log-Level in Production. ASP.NET Core loggt im Development-Modus extrem viel, was in Kubernetes Speicher kostet.
- Graceful Shutdown: .NET reagiert auf SIGTERM korrekt und beendet laufende Requests. Setze
terminationGracePeriodSecondsin Kubernetes passend.
Kubernetes Deployment und Service
Jetzt das komplette Kubernetes-Setup:
apiVersion: apps/v1
kind: Deployment
metadata:
name: dotnet-api
namespace: production
labels:
app: dotnet-api
spec:
replicas: 3
selector:
matchLabels:
app: dotnet-api
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
labels:
app: dotnet-api
spec:
terminationGracePeriodSeconds: 30
containers:
- name: api
image: mein-registry.azurecr.io/dotnet-api:v1.0
ports:
- containerPort: 8080
name: http
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
- name: ASPNETCORE_URLS
value: "http://+:8080"
- name: ConnectionStrings__Default
valueFrom:
secretKeyRef:
name: db-credentials
key: connection-string
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3
startupProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 12
---
apiVersion: v1
kind: Service
metadata:
name: dotnet-api
namespace: production
spec:
selector:
app: dotnet-api
ports:
- port: 80
targetPort: 8080
name: http
type: ClusterIP
| Einstellung | Wert | Warum |
|---|---|---|
requests.memory | 128Mi | .NET-Baseline für eine kleine API |
limits.memory | 512Mi | Puffer für GC-Spitzen und Caching |
startupProbe | 60s max | .NET braucht beim ersten Start Zeit für JIT |
terminationGracePeriodSeconds | 30 | Genug Zeit für laufende Requests |
Häufige Fehler vermeiden
GC und Memory Limits: .NET erkennt Container-Memory-Limits automatisch seit .NET 6. Der Garbage Collector passt sich an limits.memory an. Setze trotzdem immer Limits — ohne sie kennt die Runtime das Budget nicht und allokiert zu viel.
Image-Tag-Strategie: Verwende niemals latest als Tag. Nutze semantische Versionierung oder den Git-Commit-Hash:
# Image mit Git-SHA taggen
docker build -t mein-registry.azurecr.io/dotnet-api:$(git rev-parse --short HEAD) .
# Oder mit Versionsnummer
docker build -t mein-registry.azurecr.io/dotnet-api:v1.2.3 .
Culture und Timezone: .NET-Container nutzen standardmäßig die invariante Culture und UTC. Wenn deine App lokalisierte Formate braucht, setze die Umgebungsvariable DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false und installiere das icu-Paket im Dockerfile.
FAQ
Welches Base Image soll ich verwenden — aspnet, runtime oder nanoserver?
Für ASP.NET-Core-Apps nimm mcr.microsoft.com/dotnet/aspnet:8.0. Für Konsolen-Apps ohne Webserver reicht mcr.microsoft.com/dotnet/runtime:8.0. Nanoserver-Varianten sind kleiner, laufen aber nur auf Windows Nodes.
Wie debugge ich eine .NET-App im Kubernetes-Cluster?
Nutze kubectl port-forward für direkten Zugriff und kubectl logs für die Ausgabe. Für Remote-Debugging kannst du den .NET-Debugger vsdbg ins Container-Image einbauen — aber nur im Staging, niemals in Production.
Brauche ich ein Distroless Base Image?
Distroless Images von Google (gcr.io/distroless/dotnet) enthalten keine Shell und reduzieren die Angriffsfläche. Der Nachteil: Debugging im Container ist ohne Shell schwierig. Für Production-Workloads mit hohen Sicherheitsanforderungen sind sie empfehlenswert.
Wie setze ich CPU-Limits für .NET richtig?
.NET erkennt CPU-Limits und passt die Thread-Pool-Größe an. Setze requests.cpu auf den Normalverbrauch und limits.cpu auf das Doppelte bis Dreifache. Zu niedrige CPU-Limits führen zu Throttling und hohen Latenzen.
Soll ich AOT-Kompilierung für Container nutzen?
Native AOT (seit .NET 8) erzeugt ein einzelnes Binary ohne Runtime-Abhängigkeit. Startup-Zeit sinkt auf unter 50ms. Der Nachteil: kein Reflection, keine dynamische Code-Generierung. Für APIs mit minimalen Abhängigkeiten ist AOT ideal, für komplexe Anwendungen mit Entity Framework eher nicht.
Legacy zu Kubernetes migrieren?
Wir begleiten Ihre Migration von VMs zu Containern – ohne Produktionsausfall. Erfahrung aus 50+ Migrationsprojekten.
Kubernetes-Beratung gesucht?
Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.
📖 Verwandte Artikel
Weitere interessante Beiträge zu ähnlichen Themen
Node.js auf Kubernetes: Express-App deployen
Express-Apps auf Kubernetes deployen mit Multi-Stage Dockerfile, Graceful Shutdown, Resource Limits und HPA für stabile Production-Cluster.
Python Django auf Kubernetes deployen
Django-Anwendungen auf Kubernetes deployen mit Multi-Stage Dockerfile, Gunicorn, Celery-Workern und automatischen Health Checks für Production-Umgebungen.
Kubernetes Automotive ASPICE 2026: Container für SDV und Compliance
Entdecken Sie, wie Kubernetes Automotive ASPICE 2026 Standards für die SDV-Entwicklung & Compliance revolutioniert. Effiziente Entwicklung, Tests und sichere Prozesse für OEMs in Deutschland – ein entscheidender Schritt für Kubernetes Compliance Deutschland.
ArgoCD ApplicationSets: Multi-App Deployment Patterns
ArgoCD ApplicationSets automatisieren Multi-App und Multi-Cluster Deployments mit Generatoren. Praxis-Patterns für Git, Cluster und Matrix.
BSI-Grundschutz für Kubernetes-Cluster umsetzen
BSI IT-Grundschutz für Kubernetes umsetzen: Baustein SYS.1.6 Containerisierung mit Pod Security Standards, Audit-Logging und Netzwerksegmentierung.