In modern .NET core enterprise applications, monolithic architectures struggle with scaling, deployment speed, and team velocity. This guide solves that by showing you how to build, containerize, and deploy independent ASP.NET Core microservices to Kubernetes. You’ll create a production-ready catalog service that scales horizontally, handles health checks, and communicates reliably—essential for cloud-native apps that must run 24/7 with zero-downtime updates and automatic scaling.
winget install Kubernetes.kubectl on Windows or brew on macOS)minikube start)services/catalog subfolderLet’s build our first microservice—a catalog API exposing products. Start in services/catalog.
dotnet new webapi -n CatalogService --no-https -f net10.0
cd CatalogService
dotnet add package Microsoft.AspNetCore.OpenApi
Replace Program.cs with this modern minimal API using primary constructors and records:
using CatalogService.Models;
var builder = WebApplication.CreateSlimBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddHealthChecks();
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
var products = new[]
{
new Product(1, "Laptop", 999.99m),
new Product(2, "Mouse", 29.99m)
};
app.MapGet("/products", () => products)
.WithTags("Products")
.WithOpenApi();
app.MapHealthChecks("/health");
app.MapHealthChecks("/ready", HealthCheckOptions);
app.Run();
static void HealthCheckOptions(HealthCheckOptions options)
{
options.AddCheck("self", () => HealthCheckResult.Healthy());
}
Create Models/Product.cs:
namespace CatalogService.Models;
public record Product(int Id, string Name, decimal Price);
Create Dockerfile in services/catalog for optimized, production-ready images:
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY ["CatalogService.csproj", "."]
RUN dotnet restore "CatalogService.csproj"
COPY . .
RUN dotnet publish "CatalogService.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app
COPY --from=build /app/publish .
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl --fail http://localhost:8080/health || exit 1
ENTRYPOINT ["dotnet", "CatalogService.dll"]
Build and test locally:
docker build -t catalog-service:dev .
docker run -p 8080:8080 catalog-service:dev
Hit http://localhost:8080/swagger—your API is live!
Enable Kubernetes in Docker Desktop. Create k8s/ folder with these YAML files.
deployment.yaml (with probes and resource limits):
apiVersion: apps/v1
kind: Deployment
metadata:
name: catalog-deployment
spec:
replicas: 2
selector:
matchLabels:
app: catalog
template:
metadata:
labels:
app: catalog
spec:
containers:
- name: catalog
image: catalog-service:dev
ports:
- containerPort: 8080
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: catalog-service
spec:
selector:
app: catalog
ports:
- port: 80
targetPort: 8080
type: ClusterIP
Deploy:
kubectl apply -f k8s/deployment.yaml
kubectl get pods
kubectl port-forward service/catalog-service 8080:80
Access at http://localhost:8080/swagger. Scale with kubectl scale deployment catalog-deployment --replicas=3.
For environment-specific config, create configmap.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: catalog-config
data:
Logging__LogLevel__Default: "Information"
Products__MinPrice: "10.0"
---
apiVersion: v1
kind: Secret
metadata:
name: catalog-secret
type: Opaque
data:
ConnectionStrings__Db: c29tZS1iYXNlNjQtZGF0YQo= # "some-base64-data"
Mount in deployment under envFrom and volumeMounts.
Enhance with gRPC for inter-service calls and async events. Add to Program.cs:
// gRPC example for Product service
builder.Services.AddGrpc();
app.MapGrpcService<ProductService>();
// Event publishing with IMessageBroker (inject MassTransit or custom)
app.MapPost("/products", async (Product product, IMessageBroker broker) =>
{
await broker.PublishAsync(new ProductCreated(product.Id, product.Name));
return Results.Created($"/products/{product.Id}", product);
});
Use primary constructors for lean services:
public class ProductService(IMessageBroker broker) : ProductServiceBase
{
public override async Task<GetProductsResponse> GetProducts(GetProductsRequest request, ServerCallContext context)
{
// Fetch from DB or cache
await broker.PublishAsync(new ProductsQueried());
return new() { Products = { /* products */ } };
}
}
kubectl logs <pod-name>. Fix health probe paths or port mismatches.docker push to registry like Docker Hub.kubectl describe service catalog-service.dotnet-counters inside pod.envFrom: configMapRef instead of individual env vars.kubectl autoscale deployment catalog-deployment --cpu-percent=50 --min=2 --max=10.Kestrel__Limits__MaxConcurrentConnections=1000 in ConfigMap.services.AddStackExchangeRedisCache()./metrics endpoint.builder.Services.AddOpenTelemetryTracing().helm create catalog-chart.You now have a fully functional, cloud-native catalog microservice running on Kubernetes. Next, add more services (basket, ordering), wire them with an API Gateway like Ocelot, and deploy to AKS or EKS. Experiment with Istio for service mesh and CI/CD with GitHub Actions.
Use an Ingress controller like NGINX Ingress. Create an Ingress resource pointing to your service port 80, with TLS for HTTPS.
Liveness restarts unhealthy pods; readiness stops routing traffic until the app is fully initialized (e.g., DB connected).
Synchronous: gRPC or HTTP. Asynchronous: MassTransit with RabbitMQ/Kafka for events. Avoid direct DB coupling.
Yes, but per-service DBs only. Use dotnet ef migrations add in init containers for schema changes.
Store in Kubernetes Secrets or external vaults like Azure Key Vault. Mount as volumes or env vars—never hardcode.
They exclude build tools (SDK=500MB+), resulting in tiny runtime images (~100MB) that deploy faster and scale better.
kubectl exec -it <pod> -- bash, then dotnet-counters collect or attach VS Code debugger.
Deployments for stateless APIs like catalog. StatefulSets for databases needing stable identities.
Kubernetes rolling updates replace pods gradually. Use strategy: type: RollingUpdate, maxUnavailable: 0.
Build a full eShopOnContainers clone: add ordering/basket services, API Gateway, and observability with Jaeger.
Headless Architecture in .NET Microservices with gRPC
.NET 8 and Angular Apps with Keycloak In the rapidly evolving landscape of 2026, identity…
Mastering .NET 10 and C# 13: Building High-Performance APIs Together Executive Summary In modern…
NET 10 is the Ultimate Tool for AI-Native Founders The 2026 Lean .NET SaaS Stack…
Modern .NET development keeps pushing toward simplicity, clarity, and performance. With C# 12+, developers can…
Implementing .NET 10 LTS Performance Optimizations: Build Faster Enterprise Apps Together Executive Summary…
Building Production-Ready Headless Architectures with API-First .NET Executive Summary Modern applications demand flexibility across…
This website uses cookies.