
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Dropped requests during release windows remain the most common cause of user-facing incidents in enterprise .NET environments. Implementing blue-green deploys for a .NET app eliminates this risk by running two identical production environments and switching traffic only after the new version passes comprehensive health validation. This guide covers the exact Kubernetes manifests, NGINX Ingress configuration, and database migration strategies I use to deliver zero-downtime releases for ASP.NET Core services.
How do you configure blue-green deploys for a .NET app on Kubernetes?
The foundation of reliable blue-green deploys for a .NET app is treating the two environments as completely independent Deployment resources rather than relying on rolling update strategies. Rolling updates are excellent for gradual rollouts but cannot guarantee zero dropped requests during the transition window because old pods terminate while new ones are still starting. With blue-green, both versions exist simultaneously at full capacity, and the cutover is a single metadata change in your Ingress resource.
Create separate Deployment manifests
Your CI pipeline should produce two distinct Deployment YAML files or use Kustomize overlays to generate them from a single base. The critical detail is that each deployment must have unique labels so the Ingress controller can target them precisely. Here is a production-grade manifest pattern I use for ASP.NET Core 9 applications:
<!-- deployment-green.yaml -->
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-green
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: myapp
slot: green
template:
metadata:
labels:
app: myapp
slot: green
spec:
containers:
- name: myapp
image: registry.example.com/myapp:v1.3.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
env:
- name: ASPNETCORE_ENVIRONMENT
value: Production
- name: ConnectionStrings__Default
valueFrom:
secretKeyRef:
name: myapp-secrets
key: db-connection-string Notice the slot: green label. This is what your Ingress will select. Never share the same label set between blue and green deployments, or your service mesh and monitoring will conflate metrics from both versions. For teams managing multiple microservices, consider reading about blue-green and canary deploys on Kubernetes to understand when each strategy fits best.
Configure the NGINX Ingress for atomic switching
The actual traffic switch happens in the Ingress resource. During normal operation, it points to the active slot. When you are ready to promote green, you update the backend service selector. This change propagates within seconds across all NGINX replicas:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-green-svc # Change this to switch traffic
port:
number: 80 A common mistake is pointing the Ingress directly at a Deployment. Always create a dedicated Service for each slot. This decouples routing from pod lifecycle and makes the switch instantaneous because Kubernetes only needs to update endpoint slices, not reconfigure every NGINX pod individually.
What health checks are required before switching traffic?
You must never switch traffic based solely on pod readiness. ASP.NET Core applications often report ready before their dependencies are fully warmed up, leading to a burst of 500 errors immediately after cutover. Your validation gate must include three layers of checks before the Ingress switch is approved.
- Readiness probe success: The
/health/readyendpoint must return 200 for at least 3 consecutive checks across all pods. Configure this endpoint to verify database connectivity, cache availability, and downstream API reachability using the built-inMicrosoft.Extensions.Diagnostics.HealthCheckslibrary. - Synthetic transaction test: Run an actual business-critical request against the green environment's internal ClusterIP before exposing it publicly. A simple
curlto a read-only endpoint like/api/v1/products?limit=1validates serialization, authentication middleware, and query execution end-to-end. - Metric baseline comparison: Compare error rate and p99 latency of the green pods against the current blue baseline over a 2-minute observation window. If green shows more than 1% error rate or 20% higher latency, abort the promotion automatically.
This layered approach catches issues that basic HTTP health checks miss, such as misconfigured connection strings, missing environment variables, or incompatible database schema changes. Teams building observability into this process should review the four golden signals of monitoring to define meaningful thresholds for the metric comparison step.
How do you handle database migrations safely in blue-green deploys?
Database compatibility is where most blue-green implementations fail. Both blue and green versions must be able to read and write the same database simultaneously during the validation window. Breaking schema changes require a multi-phase migration strategy that maintains backward compatibility across both application versions.
The expand-and-contract pattern is non-negotiable for .NET blue-green deploys. Never drop a column or rename a table in the same deployment as the code that stops using it. Instead, follow this sequence:
- Phase 1 (Expand): Add the new column or table while keeping the old one. Deploy green with code that writes to both old and new columns but reads from the old column. Blue continues working unchanged.
- Phase 2 (Migrate data): Run a background job or migration script to backfill existing rows into the new column. Both versions continue operating normally.
- Phase 3 (Contract): After green is promoted and stable, deploy a subsequent release that removes reads from the old column. Only after this version is fully rolled out do you drop the old column in a future maintenance window.
For EF Core users, this means avoiding RenameColumn or DropColumn in migrations tied to a blue-green release. Generate additive-only migrations and defer destructive operations. Teams working with PostgreSQL should consult PostgreSQL administration essentials for safe concurrent index creation and lock management during these phases.
Blue-green vs rolling update vs canary for .NET apps?
Choosing the right deployment strategy depends on your application's tolerance for mixed-version traffic, rollback speed requirements, and infrastructure cost constraints. Each approach has distinct trade-offs for ASP.NET Core workloads.
| Criteria | Blue-Green | Rolling Update | Canary |
|---|---|---|---|
| Zero downtime guarantee | Yes (atomic switch) | No (brief overlap window) | Yes (gradual shift) |
| Rollback speed | Instant (revert Ingress) | Slow (re-roll pods) | Moderate (shift weight back) |
| Infrastructure cost | 2× during deploy | ~1.25× during deploy | ~1.1–1.5× during deploy |
| Mixed-version traffic | Never | Always during rollout | Controlled percentage |
| Best for | Critical APIs, breaking changes | Internal services, tolerant apps | User-facing features, A/B tests |
| Complexity | Medium (Ingress + 2 deploys) | Low (native K8s) | High (traffic splitting + metrics) |
In practice, I default to blue-green for customer-facing .NET APIs and payment services where even a single failed request during rollout violates SLAs. Rolling updates are fine for internal batch processors or admin dashboards. Canary releases earn their complexity when you need real-user feedback before full commitment, especially for UI-heavy Blazor or MVC applications. Understanding these trade-offs helps avoid over-engineering simple services or under-protecting critical ones.
How do you automate the promotion and rollback workflow?
Manual kubectl apply commands for Ingress switches are unacceptable in production. Automate the entire promote-or-abort decision in your CI/CD pipeline using a scripted validation stage. Here is a battle-tested GitHub Actions pattern that integrates with the health check gates described earlier:
- name: Validate Green Environment
run: |
GREEN_SVC=$(kubectl get svc myapp-green-svc -n production -o jsonpath='{.spec.clusterIP}')
# Wait for readiness
kubectl rollout status deployment/myapp-green -n production --timeout=300s
# Synthetic test
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://${GREEN_SVC}/api/v1/health/deep)
if [ "$HTTP_CODE" != "200" ]; then
echo "Deep health check failed: $HTTP_CODE"
exit 1
fi
# Metric gate (Prometheus query)
ERROR_RATE=$(curl -s 'http://prometheus:9090/api/v1/query?query=rate(http_requests_total{slot="green",status=~"5.."}[2m])' | jq -r '.data.result[0].value[1]')
THRESHOLD=0.01
if (( $(echo "$ERROR_RATE > $THRESHOLD" | bc -l) )); then
echo "Error rate $ERROR_RATE exceeds threshold"
exit 1
fi
- name: Promote Green to Active
if: success()
run: |
kubectl patch ingress myapp-ingress -n production \
--type='json' \
-p='[{"op":"replace","path":"/spec/rules/0/http/paths/0/backend/service/name","value":"myapp-green-svc"}]'
- name: Rollback on Failure
if: failure()
run: |
echo "Validation failed — keeping Blue active"
kubectl delete deployment myapp-green -n production
# Send alert to PagerDuty/Slack
curl -X POST $ALERT_WEBHOOK -d '{"text":"Green promotion aborted for myapp"}' This script enforces the validation gates programmatically and ensures the Ingress switch only executes after all checks pass. The rollback path cleans up the failed green deployment to prevent resource drift and orphaned pods. For teams using GitOps, setting up GitOps with ArgoCD provides declarative promotion through commit-based sync waves rather than imperative patches.
Implementing Blue-Green Deploys for a .NET App Reliably
Blue-green deploys for a .NET app deliver the strongest zero-downtime guarantee available today, but only when implemented with disciplined health validation, backward-compatible database migrations, and automated promotion logic. Start by establishing the dual-deployment pattern and NGINX Ingress switching described here, then layer in synthetic testing and metric gates as your observability matures. The upfront infrastructure cost of running two environments briefly is negligible compared to the revenue and trust lost from a single botched rolling update. If your team needs help designing a deployment strategy tailored to your specific .NET architecture and compliance requirements, reach out to discuss your deployment challenges.