CI/CD, Publishing & Deployment

16 questions found

What is the typical CI/CD pipeline structure for a .NET application?

Beginner
A typical pipeline includes: restore dependencies (dotnet restore), build (dotnet build), run automated tests (dotnet test), publish deployable artifacts (dotnet publish), and deploy to a target environment (container registry, cloud platform, or server) -- often gated by required checks like code coverage thresholds, security scans, and manual approval for production deployments.
# GitHub Actions example
- run: dotnet restore
- run: dotnet build --no-restore -c Release
- run: dotnet test --no-build -c Release
- run: dotnet publish -c Release -o ./publish
- run: docker build -t myapp:${{ github.sha }} .
Real-world example A team's pipeline automatically blocks merging any pull request where dotnet test fails or code coverage drops below 80%, catching regressions before they ever reach the main branch.

Common follow-ups: What's the difference between continuous integration and continuous deployment?;How do you structure a pipeline for multiple environments (dev, staging, prod)?

.NET CLI SDK & Project Structure (csproj);Docker & Containerization

How do you configure a GitHub Actions workflow to build, test, and publish a .NET application?

Intermediate
A GitHub Actions workflow YAML file defines jobs with steps using the actions/setup-dotnet action to install the required SDK version, followed by standard dotnet CLI commands, with results and artifacts optionally uploaded using actions/upload-artifact for use in subsequent deployment jobs.
name: CI
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with: { dotnet-version: '8.0.x' }
      - run: dotnet restore
      - run: dotnet build --no-restore -c Release
      - run: dotnet test --no-build -c Release --logger trx
      - uses: actions/upload-artifact@v4
        with: { name: test-results, path: '**/*.trx' }
Real-world example A team's GitHub Actions workflow runs on every pull request, automatically posting test results and coverage reports as a PR comment before a reviewer even opens the code, catching issues early in the review process.

Common follow-ups: How do you cache NuGet packages between workflow runs to speed up builds?;How do you set up matrix builds testing against multiple .NET versions?

.NET CLI SDK & Project Structure (csproj);Testing in .NET (xUnit Integration & Unit Testing)

What is the difference between blue-green deployment and canary deployment, and how do they apply to .NET applications?

Advanced
Blue-green deployment maintains two identical production environments (blue = current, green = new), routing all traffic to green only after it's fully deployed and verified, enabling instant rollback by switching traffic back to blue if issues arise. Canary deployment instead gradually shifts a small percentage of traffic to the new version, monitoring for errors before progressively increasing that percentage -- reducing blast radius of a bad deployment compared to blue-green's all-at-once cutover, at the cost of more complex traffic-splitting infrastructure.
# Canary deployment via Kubernetes traffic splitting
apiVersion: split.smi-spec.io/v1alpha3
kind: TrafficSplit
spec:
  service: myapp
  backends:
    - service: myapp-v1
      weight: 90
    - service: myapp-v2
      weight: 10  # only 10% of traffic hits the new version initially
Real-world example A payments API uses canary deployment, routing 5% of traffic to a new version for an hour while monitoring error rates and latency, only proceeding to 100% rollout if no anomalies are detected, versus a less risk-averse internal tool that uses simpler blue-green cutover.

Common follow-ups: How do you automate canary rollback based on error rate thresholds?;What infrastructure is needed to support traffic splitting for canary deployments?

Microservices & Distributed Architecture Patterns;Diagnostics & Performance

How do you securely manage secrets (connection strings, API keys) in a CI/CD pipeline without committing them to source control?

Intermediate
CI/CD platforms provide encrypted secret storage (GitHub Actions Secrets, Azure DevOps variable groups with secret variables, GitLab CI/CD variables) injected as environment variables only during pipeline execution, never exposed in logs or the repository itself -- combined with cloud-native secret managers (Azure Key Vault, AWS Secrets Manager) for runtime application secrets, keeping build-time and run-time secret concerns properly separated.
# GitHub Actions: referencing a repository secret
- name: Deploy
  env:
    CONNECTION_STRING: ${{ secrets.PROD_DB_CONNECTION_STRING }}
  run: dotnet run --project deploy-tool
Real-world example A pipeline deploying to Azure retrieves its deployment credentials from GitHub Actions Secrets rather than a hardcoded config file, and the application itself retrieves its database connection string from Azure Key Vault at runtime rather than from pipeline-injected environment variables.

Common follow-ups: Why shouldn't runtime application secrets be the same as CI/CD pipeline secrets?;How do you rotate a secret without pipeline downtime?

Secrets Management & Configuration Providers (Key Vault User Secrets);Configuration & Options

How do you set up automated database migrations as part of a .NET CI/CD deployment pipeline safely?

Advanced
Common safe approaches include: running EF Core migrations (`dotnet ef database update`) as a separate pipeline step before deploying application code (so the schema is ready before new code that depends on it runs), using the expand-contract pattern for backward-compatible schema changes so old and new application versions can both run against the migrated schema briefly during rollout, and always taking a database backup or snapshot immediately before applying migrations in production as a safety net.
# Pipeline step: apply migrations before deploying app
- name: Apply migrations
  run: dotnet ef database update --project src/MyApp.Data --connection "$PROD_CONNECTION_STRING"

- name: Deploy application
  run: az webapp deploy --resource-group myapp-rg --name myapp --src-path ./publish.zip
Real-world example A team's deployment pipeline runs EF Core migrations as a distinct, monitored step with automatic rollback triggers, deliberately separated from the application deployment step so a failed migration halts the pipeline before any new application code goes live against a broken schema.

Common follow-ups: What's the risk of running migrations automatically on every application startup instead of as a pipeline step?;How do you handle a migration that must run against a very large table without locking it for too long?

Entity Framework Core & Data Access;Diagnostics & Performance

What is the purpose of separate build and release pipelines (or stages), and why shouldn't you rebuild the application for each deployment environment?

Intermediate
Separating build (compile once, producing a single versioned, immutable artifact) from release (deploy that same exact artifact to dev, then staging, then production) ensures you're testing and deploying the literal same binary throughout the pipeline, eliminating the risk of environment-specific build differences causing 'it worked in staging but not production' bugs -- environment-specific configuration should be injected at deploy time (via config files or environment variables), not baked in via a fresh rebuild per environment.
# Build once
dotnet publish -c Release -o ./artifacts

# Deploy the SAME artifact to each environment, varying only config
# Deploy to staging: uses staging appsettings/env vars
# Deploy to production: uses production appsettings/env vars (same binary)
Real-world example A team eliminates a class of 'works in staging, breaks in production' bugs by switching from rebuilding the app separately for each environment to building one artifact once and promoting that exact same build through staging and then production.

Common follow-ups: How do you inject environment-specific configuration into an already-built artifact?;What's the risk of environment-specific compiler directives (#if) breaking this guarantee?

Configuration & Options;.NET CLI SDK & Project Structure (csproj)

How does deployment slot swapping (e.g., Azure App Service deployment slots) enable near-zero-downtime deployments?

Advanced
Deployment slots let you deploy a new version to a separate, fully warmed-up staging slot (with its own URL for testing) and then perform an atomic swap that exchanges the staging and production slot's routing, making the new version live instantly with the previously-production version now sitting in staging (as an instant rollback target) -- avoiding the cold-start delay and brief unavailability of a traditional stop-deploy-start deployment.
az webapp deployment slot create --name myapp --resource-group myapp-rg --slot staging
az webapp deployment source config-zip --resource-group myapp-rg --name myapp --slot staging --src ./publish.zip
# After verifying staging slot works correctly:
az webapp deployment slot swap --resource-group myapp-rg --name myapp --slot staging --target-slot production
Real-world example An e-commerce site deploys a new version to a staging slot, runs automated smoke tests against its dedicated URL, and only then swaps it into production, keeping the previous version instantly available in the staging slot for immediate rollback if any issue is discovered post-swap.

Common follow-ups: What happens to in-flight requests during a slot swap?;How do slot-specific app settings (like connection strings) avoid being swapped along with the code?

Docker & Containerization;Health Checks & Readiness/Liveness Probes

What is the role of automated smoke tests and health checks immediately after a deployment completes?

Intermediate
Post-deployment smoke tests (a small suite of critical-path checks, like 'can the app respond to a basic request' or 'can it reach the database') and health check endpoint verification confirm the newly deployed version is actually functioning correctly before fully committing to it or routing significant traffic to it, catching deployment-specific issues (missing config, failed migration, broken dependency) immediately rather than discovering them from user-facing errors or alerts minutes or hours later.
# Pipeline step after deployment
- name: Smoke test
  run: |
    response=$(curl -s -o /dev/null -w "%{http_code}" https://myapp.com/health)
    if [ "$response" != "200" ]; then
      echo "Health check failed, initiating rollback"
      exit 1
    fi
Real-world example A pipeline automatically triggers a rollback to the previous deployment slot if the post-deployment smoke test against the /health endpoint doesn't return 200 within 60 seconds of the swap, catching a bad deployment before real users are affected.

Common follow-ups: What's the difference between a smoke test and a full regression test suite for this purpose?;How quickly should a rollback be triggered after a failed smoke test?

Health Checks & Readiness/Liveness Probes;Diagnostics & Performance

How does GitOps (e.g., using Flux or ArgoCD with Kubernetes) change the deployment model compared to traditional push-based CI/CD pipelines?

Advanced
In GitOps, a Git repository is the single source of truth for the desired deployment state (Kubernetes manifests, Helm charts), and a controller running inside the cluster continuously reconciles the actual cluster state to match what's declared in Git (pull-based), rather than a CI pipeline directly pushing changes to the cluster (push-based) -- this provides a full audit trail via Git history, easy rollback (revert the Git commit), and eliminates the need to grant CI systems direct write credentials to production infrastructure.
# GitOps flow:
# 1. Developer merges PR updating image tag in deployment.yaml
# 2. Flux/ArgoCD controller (running IN the cluster) detects the Git change
# 3. Controller pulls the new manifest and reconciles cluster state to match
# CI pipeline never has direct cluster write access
Real-world example A platform team adopts ArgoCD so that rolling back a bad .NET microservice deployment is as simple as reverting a Git commit, with the in-cluster controller automatically detecting and applying the reverted state within seconds, without needing direct pipeline access to production credentials.

Common follow-ups: How does GitOps improve the security posture compared to push-based deployment?;What happens if someone makes a manual, undocumented change directly to the cluster?

Docker & Containerization;Microservices & Distributed Architecture Patterns

How do you version and tag Docker images for a .NET application as part of a CI/CD pipeline?

Intermediate
A common convention tags images with both a unique, traceable identifier (like the Git commit SHA, ensuring every build is uniquely identifiable and immutable) and a semantic or environment-meaningful tag (like 'latest' for the most recent build, or a version number for releases), pushing both tags to the container registry so deployments can reference either the exact commit or a more human-friendly version.
docker build -t myregistry.io/myapp:${{ github.sha }} -t myregistry.io/myapp:v1.4.0 .
docker push myregistry.io/myapp:${{ github.sha }}
docker push myregistry.io/myapp:v1.4.0
Real-world example A team always deploys using the specific Git SHA tag (never 'latest') in their Kubernetes manifests, guaranteeing that a rollback or audit can always trace exactly which commit is running in production at any point in time.

Common follow-ups: Why is deploying with the 'latest' tag considered a bad practice for production?;How do you clean up old, unused image tags from a registry over time?

Docker & Containerization;.NET CLI SDK & Project Structure (csproj)

Showing 1–10 of 16