You’ve built solid microservices. Now they need a home.
In this part, we’ll:
✅ Containerize .NET 8 services with Docker
✅ Run locally with Docker Compose
✅ Deploy to Kubernetes with readiness/liveness probes, scaling, and Ingress
✅ Manage configuration securely with ConfigMaps & Secrets
✅ Add autoscaling and outline CI/CD
By the end, your microservices will be cloud-ready & production-ready 🚀
1️⃣ Containerize a .NET 8 Service (Multi-Stage Build)
Let’s containerize product-service with a multi-stage Dockerfile.
📄 ProductService/Dockerfile
# ---------- Build stage ----------
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# copy sln and csproj for cached restores
COPY ProductService/*.csproj ProductService/
RUN dotnet restore ProductService/ProductService.csproj
# copy the rest
COPY ProductService/. ProductService/
WORKDIR /src/ProductService
RUN dotnet publish -c Release -o /app /p:UseAppHost=false
# ---------- Runtime stage ----------
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
# security: run as non-root
RUN addgroup --system app && adduser --system --ingroup app app
USER app
# expose http
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
COPY --from=build /app ./
ENTRYPOINT ["dotnet", "ProductService.dll"]
👉 Build & run locally:
docker build -t product-service:1.0 -f ProductService/Dockerfile .
docker run -p 8081:8080 --name product product-service:1.0
# Test
curl http://localhost:8081/health
✅ Best practices included:
- Multi-stage → small, fast images
- Run as non-root
- ASPNETCORE_URLS bound to container port
- Uses .NET 8 base images
2️⃣ Local Dev with Docker Compose
📄 docker-compose.yml
version: "3.9"
services:
product-service:
build:
context: .
dockerfile: ProductService/Dockerfile
image: product-service:1.0
environment:
- ASPNETCORE_ENVIRONMENT=Development
- Redis__ConnectionString=redis:6379
- Rabbit__Host=rabbitmq
ports:
- "8081:8080"
depends_on:
- redis
- rabbitmq
order-service:
build:
context: .
image: order-service:1.0
environment:
- ASPNETCORE_ENVIRONMENT=Development
- Rabbit__Host=rabbitmq
ports:
- "8082:8080"
depends_on:
- rabbitmq
redis:
image: redis:7
ports: ["6379:6379"]
rabbitmq:
image: rabbitmq:3.13-management
ports:
- "5672:5672"
- "15672:15672" # UI: http://localhost:15672 (guest/guest)
Run everything:
docker compose up --build
Test:
curl http://localhost:8081/ready
curl http://localhost:8082/health
3️⃣ Push Images to a Registry
docker tag product-service:1.0 yourdockerhub/product-service:1.0
docker push yourdockerhub/product-service:1.0
(Repeat for order-service.)
4️⃣ Kubernetes Namespace
kubectl create namespace shop
5️⃣ ConfigMaps & Secrets
📄 k8s/config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: product-config
namespace: shop
data:
ASPNETCORE_ENVIRONMENT: "Production"
Redis__ConnectionString: "redis:6379"
Rabbit__Host: "rabbitmq"
📄 k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: product-secrets
namespace: shop
type: Opaque
stringData:
ConnectionStrings__AppDb: "Server=sql;Database=ProductsDb;User Id=sa;Password=Your_P@ss123;TrustServerCertificate=true"
Apply:
kubectl apply -f k8s/config.yaml
kubectl apply -f k8s/secret.yaml
👉 For production → use Azure Key Vault / AWS Secrets Manager.
6️⃣ Deployment + Service + Probes
📄 k8s/product-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: product-service
namespace: shop
spec:
replicas: 2
selector:
matchLabels: { app: product-service }
strategy:
type: RollingUpdate
template:
metadata:
labels: { app: product-service }
spec:
containers:
- name: product-service
image: yourdockerhub/product-service:1.0
ports:
- containerPort: 8080
envFrom:
- configMapRef: { name: product-config }
- secretRef: { name: product-secrets }
readinessProbe:
httpGet: { path: /ready, port: 8080 }
initialDelaySeconds: 5
livenessProbe:
httpGet: { path: /health, port: 8080 }
initialDelaySeconds: 10
resources:
requests: { cpu: "100m", memory: "128Mi" }
limits: { cpu: "500m", memory: "512Mi" }
securityContext:
runAsUser: 1000
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
---
apiVersion: v1
kind: Service
metadata:
name: product-service
namespace: shop
spec:
selector: { app: product-service }
ports:
- port: 80
targetPort: 8080
Apply:
kubectl apply -f k8s/product-deploy.yaml
7️⃣ Autoscaling with HPA
📄 k8s/product-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: product-service
namespace: shop
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: product-service
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
Apply:
kubectl apply -f k8s/product-hpa.yaml
👉 For queue/event-driven workloads → use KEDA.
8️⃣ Ingress (NGINX) + TLS
📄 k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shop-ingress
namespace: shop
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts: [ "shop.local" ]
secretName: shop-tls
rules:
- host: shop.local
http:
paths:
- path: /products
pathType: Prefix
backend:
service:
name: product-service
port: { number: 80 }
- path: /orders
pathType: Prefix
backend:
service:
name: order-service
port: { number: 80 }
Generate self-signed TLS (dev only):
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout shop.key -out shop.crt \
-subj "/CN=shop.local/O=dev"
kubectl create secret tls shop-tls -n shop --key shop.key --cert shop.crt
9️⃣ Rolling Updates & Zero-Downtime
- Readiness probe → traffic only to healthy pods
- Liveness probe → restarts hung pods
Rollback if needed:
kubectl rollout status deploy/product-service -n shop
kubectl rollout undo deploy/product-service -n shop
🔟 CI/CD Outline (GitHub Actions)
📄 .github/workflows/cd.yml
name: build-and-deploy
on:
push:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with: { dotnet-version: '8.0.x' }
- run: dotnet test --nologo
- run: docker build -t ghcr.io/you/product-service:${{ github.sha }} -f ProductService/Dockerfile .
- run: echo $CR_PAT | docker login ghcr.io -u you --password-stdin
- run: docker push ghcr.io/you/product-service:${{ github.sha }}
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- uses: azure/setup-kubectl@v4
- run: |
kubectl set image deploy/product-service -n shop product-service=ghcr.io/you/product-service:${{ github.sha }}
kubectl rollout status deploy/product-service -n shop
👉 For structured deployments → use Helm or Kustomize.
✅ Production Checklist
- [ ] Run containers as non-root
- [ ] Readiness & liveness probes tuned
- [ ] Resource requests/limits set → enable HPA
- [ ] Centralized logs + traces + metrics
- [ ] Secrets from vaults (not configs)
- [ ] Network Policies (deny-all → allow needed)
- [ ] PodDisruptionBudgets for HA
- [ ] Blue/green or canary deploys (Ingress / Argo Rollouts)
- [ ] Backups + DR plan
🎯 Recap
You now have a repeatable deployment path for .NET 8 microservices:
- Build secure, small images (multi-stage, non-root)
- Run locally with docker-compose
- Deploy to Kubernetes with probes, scaling, Ingress & TLS
- Automate with CI/CD + Helm/Kustomize
✨ In the final part (Part 12) → we’ll lock it all down with: Link
- 🔑 Identity & Security (IdentityServer / Azure AD)
- 🔐 mTLS between services
- 🛡 API Gateway authorization
Comments
Post a Comment