Blue-Green Deploys for a .NET App

Khimananda Oli 9 min read Programming and Languages
Blue-Green Deploys for a .NET App

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.

NGINX IngressBlue (.NET v1.2)Active TrafficGreen (.NET v1.3)Validating / IdleShared DatabasePostgreSQL / SQL Server
Blue-green deploy architecture: NGINX Ingress routes all live traffic to the active Blue environment while Green is validated before the atomic switch.

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.

  1. Readiness probe success: The /health/ready endpoint 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-in Microsoft.Extensions.Diagnostics.HealthChecks library.
  2. Synthetic transaction test: Run an actual business-critical request against the green environment's internal ClusterIP before exposing it publicly. A simple curl to a read-only endpoint like /api/v1/products?limit=1 validates serialization, authentication middleware, and query execution end-to-end.
  3. 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.

Deploy GreenPods RunningReadiness Probe/health/ready × 3Synthetic TestGET /api/v1/productsMetric BaselineError <1%, p99 OKSwitch✗ Fail → Abort & Alert✗ Fail → Abort & Alert✗ Fail → Abort & Alert
Validation pipeline for blue-green deploys: each gate must pass sequentially before the Ingress switch is executed; any failure triggers immediate abort.

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.

CriteriaBlue-GreenRolling UpdateCanary
Zero downtime guaranteeYes (atomic switch)No (brief overlap window)Yes (gradual shift)
Rollback speedInstant (revert Ingress)Slow (re-roll pods)Moderate (shift weight back)
Infrastructure cost2× during deploy~1.25× during deploy~1.1–1.5× during deploy
Mixed-version trafficNeverAlways during rolloutControlled percentage
Best forCritical APIs, breaking changesInternal services, tolerant appsUser-facing features, A/B tests
ComplexityMedium (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.

Rollback Speed & Cost ComparisonBlue-GreenRollback: <5s | Cost: 2×Rolling UpdateRollback: 2–5min | Cost: 1.25×CanaryRollback: 30–60s | Cost: 1.3×When to Choose Each● Blue-Green:SLA-critical APIs, breaking DB changes● Rolling:Internal tools, stateless workers● Canary:User-facing features, risk validationCost multiplier = peak extra capacity during deployRollback time = time to restore previous stable state
Trade-off matrix comparing rollback speed and infrastructure overhead across the three primary .NET deployment strategies in 2026.

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.

Frequently Asked Questions

It runs two identical production environments. Traffic switches instantly from the current blue version to the new green version after validation, enabling zero-downtime releases and immediate rollbacks for ASP.NET Core applications.

Blue-green shifts all traffic at once between two full stacks. Canary routes a small percentage gradually. Blue-green suits stateless .NET APIs needing instant rollback, while canary tests risky changes with partial user exposure.

Azure App Service slots provide native swap functionality. Deployment slots share configuration but isolate code, allowing warm-up and verification before swapping VIPs to route live traffic to the green .NET instance.

Yes. Use Ingress controllers or service mesh tools like Linkerd to shift traffic between two distinct Deployments. Ensure your .NET app handles graceful shutdown signals to prevent dropped requests during the switch.

Apply backward-compatible migrations first. The green .NET app must work with the existing schema before the swap. Run destructive changes only after confirming the green deployment is stable and serving traffic successfully.

Temporarily, yes. You pay for both environments during deployment and validation. Using Premium V3 tiers allows slot swapping without extra compute costs, minimizing the financial overhead compared to maintaining separate App Service Plans.

Monitor for at least fifteen minutes or one full health check cycle. Verify application insights metrics, error rates, and latency match baseline thresholds before routing production traffic to the new .NET build.

In-process sessions are lost. Configure ASP.NET Core to use Redis Cache or SQL Server for distributed session state. This ensures users remain authenticated and retain cart data across the blue-green transition.

Use Azure DevOps or GitHub Actions with the Azure Web App Swap task. Add pre-swap validation steps that query health endpoints. Fail the pipeline automatically if the green slot returns non-200 responses.

Yes, but startup times are longer. Enable Application Initialization to preload pages and JIT compile code before the swap completes. This prevents cold-start latency spikes when traffic hits the newly activated green instance.

Access the staging slot via its unique URL provided by Azure. Bypass DNS entirely to validate functionality, authentication flows, and third-party integrations on the green .NET app before making it publicly visible.

Usually insufficient warm-up or slow dependency connections. Configure custom warm-up requests in azure-webapp-warmup.xml targeting critical API endpoints. Ensure connection pools initialize fully before the platform routes external traffic to the slot.

Yes. Mark connection strings as slot-specific in Azure Portal settings. This prevents the green slot from accidentally writing to the production database during testing and ensures correct environment isolation post-swap.

Typically under thirty seconds for most .NET apps. The operation swaps virtual IP addresses rather than moving files. Duration depends on warm-up configuration complexity and the number of host headers being rebound.

Avoid it for massive stateful applications where duplicating infrastructure is cost-prohibitive or when database schemas require breaking changes that cannot be made backward compatible. Consider rolling updates or feature flags instead.