[{"data":1,"prerenderedAt":106},["ShallowReactive",2],{"qa-\u002Fdotnet\u002Fperformance-deployment\u002Fdeployment":3},{"page":4,"siblings":94,"blog":103},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":85,"related":86,"seo":87,"seoDescription":88,"stem":89,"subtopic":6,"topic":90,"topicSlug":91,"updated":92,"__hash__":93},"qa\u002Fdotnet\u002Fperformance-deployment\u002Fdeployment.md","Deployment",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"hard","md",".NET Core","dotnet",{},true,3,"\u002Fdotnet\u002Fperformance-deployment\u002Fdeployment",[23,28,33,37,41,45,49,53,57,61,65,69,73,77,81],{"id":24,"difficulty":25,"q":26,"a":27},"dotnet-publish-modes","easy","What are the different publish modes for a .NET application and when do you use each?","`dotnet publish` supports three deployment models: **framework-dependent**,\n**self-contained**, and **single-file**. Each trades off binary size, portability,\nand startup time differently.\n\n```bash\n# Framework-dependent (default) — requires .NET runtime installed on host:\ndotnet publish -c Release\n# Output: ~500 KB of app DLLs + app.exe shim\n# Advantage: small, shares runtime with other apps on the host\n\n# Self-contained — includes the .NET runtime in the output:\ndotnet publish -c Release --self-contained true -r linux-x64\n# Output: ~70 MB including runtime\n# Advantage: no runtime required on host; portable to fresh machines\n\n# Single-file — bundles everything into one executable:\ndotnet publish -c Release --self-contained true -r linux-x64 \\\n    -p:PublishSingleFile=true\n# Output: one ~70 MB file\n# Advantage: simple deployment; one file to copy\u002Fmove\n\n# ReadyToRun (R2R) — ahead-of-time compile for faster startup:\ndotnet publish -c Release -r linux-x64 \\\n    -p:PublishReadyToRun=true\n\n# Trimming — remove unused framework code (careful: reflection-heavy code may break):\ndotnet publish -c Release --self-contained true -r linux-x64 \\\n    -p:PublishTrimmed=true\n# Output: ~20-40 MB depending on what's used\n```\n\n| Mode | Host requirement | Binary size | Startup |\n|------|-----------------|-------------|---------|\n| Framework-dependent | .NET runtime | Small | Normal |\n| Self-contained | Nothing | ~70 MB | Normal |\n| Single-file | Nothing | ~70 MB (one file) | Slightly slower |\n| R2R | Nothing | Larger | Faster |\n\n**Rule of thumb:** Use framework-dependent for servers where you control the\nruntime version. Use self-contained for containers (Docker handles the runtime)\nor edge deployments where installing the runtime is not possible.\n",{"id":29,"difficulty":30,"q":31,"a":32},"kestrel-reverse-proxy","medium","What is Kestrel and why is it typically used behind a reverse proxy in production?","**Kestrel** is ASP.NET Core's built-in cross-platform HTTP server. It handles\nraw TCP\u002FTLS connections and is fast enough for production traffic, but in\nmost deployments it runs behind a reverse proxy (Nginx, Apache, or IIS) that\nhandles TLS termination, static files, rate limiting, and connection management.\n\n```csharp\n\u002F\u002F Program.cs — Kestrel is the default; configure limits explicitly:\nbuilder.WebHost.ConfigureKestrel(opts =>\n{\n    opts.Limits.MaxConcurrentConnections         = 1000;\n    opts.Limits.MaxRequestBodySize               = 10 * 1024 * 1024; \u002F\u002F 10 MB\n    opts.Limits.MinRequestBodyDataRate           = null;  \u002F\u002F disable for uploads\n    opts.Limits.RequestHeadersTimeout            = TimeSpan.FromSeconds(30);\n\n    \u002F\u002F HTTPS directly on Kestrel (without a reverse proxy):\n    opts.ListenAnyIP(443, listenOpts =>\n        listenOpts.UseHttps(\"\u002Fetc\u002Fssl\u002Fcerts\u002Fapp.pfx\", \"cert-password\"));\n\n    \u002F\u002F HTTP\u002F2 and HTTP\u002F3:\n    opts.ListenAnyIP(5000, listenOpts =>\n        listenOpts.Protocols = HttpProtocols.Http1AndHttp2);\n});\n\n\u002F\u002F Behind a reverse proxy — forward headers so HTTPS links are correct:\napp.UseForwardedHeaders(new ForwardedHeadersOptions\n{\n    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto,\n});\n\u002F\u002F Must come BEFORE UseAuthentication \u002F UseRouting\n\n\u002F\u002F Nginx config snippet (reverse proxy to Kestrel on port 5000):\n\u002F\u002F server {\n\u002F\u002F listen 443 ssl;\n\u002F\u002F location \u002F {\n\u002F\u002F proxy_pass         http:\u002F\u002Flocalhost:5000;\n\u002F\u002F proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;\n\u002F\u002F proxy_set_header   X-Forwarded-Proto $scheme;\n\u002F\u002F }\n\u002F\u002F }\n```\n\n**Rule of thumb:** Always call `UseForwardedHeaders` when running behind a\nreverse proxy. Without it, `Request.Scheme` returns `http` even for HTTPS\nrequests, breaking redirect URLs and cookie `Secure` flags.\n",{"id":34,"difficulty":25,"q":35,"a":36},"environment-config","How does ASP.NET Core handle configuration across different environments?","ASP.NET Core layers configuration from multiple sources in priority order.\nHigher-priority sources override lower ones. The `ASPNETCORE_ENVIRONMENT`\nenvironment variable selects environment-specific files.\n\n```csharp\n\u002F\u002F Default configuration loading order (last wins):\n\u002F\u002F 1. appsettings.json              — base config, committed to source control\n\u002F\u002F 2. appsettings.{Environment}.json — environment overrides (Development, Production)\n\u002F\u002F 3. Environment variables          — injected by host OS \u002F orchestrator \u002F Docker\n\u002F\u002F 4. Command-line arguments         — highest priority, useful for one-off overrides\n\n\u002F\u002F appsettings.json:\n\u002F\u002F { \"ConnectionStrings\": { \"Default\": \"Server=localhost;Database=dev\" } }\n\n\u002F\u002F appsettings.Production.json:\n\u002F\u002F { \"ConnectionStrings\": { \"Default\": \"Server=prod-db;Database=app\" } }\n\n\u002F\u002F Environment variables override any file:\n\u002F\u002F ConnectionStrings__Default=Server=prod-db;Database=app\n\u002F\u002F (double underscore = colon separator for nested keys)\n\n\u002F\u002F Access in code:\nvar connStr = builder.Configuration.GetConnectionString(\"Default\");\n\n\u002F\u002F Secrets in development — user secrets (never committed):\n\u002F\u002F dotnet user-secrets set \"ConnectionStrings:Default\" \"Server=localhost;...\"\n\n\u002F\u002F Secrets in production — environment variables or Azure Key Vault:\nbuilder.Configuration.AddAzureKeyVault(\n    new Uri($\"https:\u002F\u002F{vaultName}.vault.azure.net\u002F\"),\n    new DefaultAzureCredential());\n\n\u002F\u002F ASPNETCORE_ENVIRONMENT controls which appsettings file is loaded:\n\u002F\u002F Development → appsettings.Development.json\n\u002F\u002F Staging      → appsettings.Staging.json\n\u002F\u002F Production   → appsettings.Production.json (default if not set)\n```\n\n**Rule of thumb:** Commit `appsettings.json` and environment-specific files\nwith non-sensitive defaults. Never commit secrets — use environment variables\nin production and `dotnet user-secrets` in development.\n",{"id":38,"difficulty":30,"q":39,"a":40},"docker-multistage","How do you write a production-ready multi-stage Dockerfile for an ASP.NET Core application?","A **multi-stage Dockerfile** uses the SDK image to build and the smaller\nruntime image to run, keeping the final image lean and free of build tools.\n\n```dockerfile\n# Stage 1: restore and build (uses full SDK)\nFROM mcr.microsoft.com\u002Fdotnet\u002Fsdk:8.0 AS build\nWORKDIR \u002Fsrc\n\n# Copy project files first to leverage layer caching:\nCOPY [\"MyApp\u002FMyApp.csproj\", \"MyApp\u002F\"]\nRUN dotnet restore \"MyApp\u002FMyApp.csproj\"\n\n# Copy source and publish:\nCOPY . .\nWORKDIR \u002Fsrc\u002FMyApp\nRUN dotnet publish \"MyApp.csproj\" -c Release -o \u002Fapp\u002Fpublish \\\n    --no-restore\n\n# Stage 2: final runtime image (no SDK, no build tools)\nFROM mcr.microsoft.com\u002Fdotnet\u002Faspnet:8.0 AS final\nWORKDIR \u002Fapp\n\n# Run as non-root (security hardening):\nRUN useradd --uid 1001 --no-create-home appuser\nUSER appuser\n\n# Copy only the publish output:\nCOPY --from=build \u002Fapp\u002Fpublish .\n\nENV ASPNETCORE_ENVIRONMENT=Production\nENV ASPNETCORE_URLS=http:\u002F\u002F+:8080\n\nEXPOSE 8080\nENTRYPOINT [\"dotnet\", \"MyApp.dll\"]\n```\n\n```bash\n# Build and run:\ndocker build -t myapp:latest .\ndocker run -p 8080:8080 \\\n    -e ConnectionStrings__Default=\"Server=db;Database=app\" \\\n    myapp:latest\n```\n\nMulti-stage builds reduce the final image from ~1 GB (SDK) to ~250 MB (runtime).\nRunning as a non-root user prevents the container process from escalating\nprivileges if compromised.\n\n**Rule of thumb:** Always copy the project file and run `dotnet restore` as a\nseparate `COPY`\u002F`RUN` step before copying source code. Docker caches the restore\nlayer until the `.csproj` changes, making subsequent builds much faster.\n",{"id":42,"difficulty":30,"q":43,"a":44},"health-probe-kubernetes","How do you configure Kubernetes liveness and readiness probes for an ASP.NET Core app?","Kubernetes uses **liveness** probes to restart an unhealthy container and\n**readiness** probes to stop routing traffic to a container that is not ready.\nASP.NET Core's health checks map directly to these two probe types.\n\n```csharp\n\u002F\u002F Program.cs — separate endpoints for liveness and readiness:\nbuilder.Services.AddHealthChecks()\n    .AddDbContextCheck\u003CAppDbContext>(tags: [\"readiness\"])\n    .AddRedis(\"localhost:6379\",      tags: [\"readiness\"]);\n\napp.MapHealthChecks(\"\u002Fhealth\u002Flive\", new HealthCheckOptions\n{\n    Predicate = _ => false,  \u002F\u002F always 200 if process is running\n});\n\napp.MapHealthChecks(\"\u002Fhealth\u002Fready\", new HealthCheckOptions\n{\n    Predicate = check => check.Tags.Contains(\"readiness\"),\n    ResultStatusCodes =\n    {\n        [HealthStatus.Healthy]   = StatusCodes.Status200OK,\n        [HealthStatus.Degraded]  = StatusCodes.Status200OK,   \u002F\u002F still route traffic\n        [HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable,\n    },\n});\n```\n\n```yaml\n# Kubernetes deployment — liveness and readiness probes:\nspec:\n  containers:\n  - name: myapp\n    image: myapp:latest\n    ports:\n    - containerPort: 8080\n    livenessProbe:\n      httpGet:\n        path: \u002Fhealth\u002Flive\n        port: 8080\n      initialDelaySeconds: 5   # wait for startup\n      periodSeconds: 10\n      failureThreshold: 3      # restart after 3 consecutive failures\n    readinessProbe:\n      httpGet:\n        path: \u002Fhealth\u002Fready\n        port: 8080\n      initialDelaySeconds: 10\n      periodSeconds: 5\n      failureThreshold: 2      # pull from load balancer after 2 failures\n```\n\n**Rule of thumb:** Liveness checks should only verify the process is not deadlocked\n(no external dependency checks). Readiness checks should verify all dependencies\nthe app needs to serve traffic are available. A liveness failure restarts the pod;\na readiness failure only removes it from the load balancer.\n",{"id":46,"difficulty":30,"q":47,"a":48},"graceful-shutdown","How do you implement graceful shutdown in ASP.NET Core?","**Graceful shutdown** lets the app finish in-flight requests before stopping.\nASP.NET Core handles SIGTERM automatically but the default shutdown timeout\n(5 s) may be too short for long-running requests.\n\n```csharp\n\u002F\u002F Increase the shutdown timeout:\nbuilder.Host.ConfigureHostOptions(opts =>\n    opts.ShutdownTimeout = TimeSpan.FromSeconds(30));\n\n\u002F\u002F Or per web host:\nbuilder.WebHost.UseShutdownTimeout(TimeSpan.FromSeconds(30));\n\n\u002F\u002F IHostedService — implement long-running background work with cancellation:\npublic class OrderProcessorService : BackgroundService\n{\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        while (!stoppingToken.IsCancellationRequested)\n        {\n            var order = await _queue.DequeueAsync(stoppingToken);\n            if (order is null) continue;\n\n            try   { await ProcessOrderAsync(order, stoppingToken); }\n            catch (OperationCanceledException) { break; } \u002F\u002F shutting down — stop cleanly\n            catch (Exception ex)               { _logger.LogError(ex, \"Processing failed\"); }\n        }\n        _logger.LogInformation(\"OrderProcessorService stopped gracefully\");\n    }\n}\n\n\u002F\u002F IHostApplicationLifetime — hook into specific lifecycle events:\npublic class StartupService : IHostedService\n{\n    private readonly IHostApplicationLifetime _lifetime;\n\n    public Task StartAsync(CancellationToken ct)\n    {\n        _lifetime.ApplicationStopping.Register(() =>\n            _logger.LogInformation(\"Shutdown signal received\"));\n        return Task.CompletedTask;\n    }\n    public Task StopAsync(CancellationToken ct) => Task.CompletedTask;\n}\n```\n\nIn Kubernetes, a `preStop` hook can add a sleep before SIGTERM to let the\nload balancer drain connections before the process starts shutting down:\n\n```yaml\nlifecycle:\n  preStop:\n    exec:\n      command: [\"\u002Fbin\u002Fsh\", \"-c\", \"sleep 5\"]\n```\n\n**Rule of thumb:** Always pass `CancellationToken` through all async operations\nin background services. If you don't, a shutdown signal is ignored and the\nprocess may be forcibly killed, dropping in-flight work.\n",{"id":50,"difficulty":30,"q":51,"a":52},"azure-app-service","How do you deploy an ASP.NET Core application to Azure App Service?","Azure App Service hosts ASP.NET Core applications without managing virtual\nmachines. Deployment can be done via GitHub Actions, Azure DevOps, or the\nAzure CLI.\n\n```bash\n# Create an App Service Plan and Web App via Azure CLI:\naz group create --name myapp-rg --location eastus\naz appservice plan create --name myapp-plan --resource-group myapp-rg \\\n    --sku B1 --is-linux\naz webapp create --resource-group myapp-rg --plan myapp-plan \\\n    --name myapp-prod --runtime \"DOTNETCORE|8.0\"\n\n# Deploy from local publish output:\ndotnet publish -c Release -o .\u002Fpublish\naz webapp deployment source config-zip \\\n    --resource-group myapp-rg --name myapp-prod \\\n    --src .\u002Fpublish.zip\n\n# Configure app settings (environment variables):\naz webapp config appsettings set --resource-group myapp-rg --name myapp-prod \\\n    --settings \"ConnectionStrings__Default=Server=...\" \\\n               \"ASPNETCORE_ENVIRONMENT=Production\"\n```\n\n```yaml\n# GitHub Actions workflow:\n- name: Deploy to Azure App Service\n  uses: azure\u002Fwebapps-deploy@v3\n  with:\n    app-name: myapp-prod\n    publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}\n    package: .\u002Fpublish\n```\n\nKey App Service features for .NET:\n- **Deployment slots** — blue\u002Fgreen deployments with warm-up\n- **Auto-scaling** — scale out based on CPU or HTTP queue depth\n- **Managed Identity** — access Key Vault without storing credentials\n- **Health check integration** — automatically restart unhealthy instances\n\n**Rule of thumb:** Use App Service deployment slots for zero-downtime releases —\ndeploy to a staging slot, warm it up, then swap to production in a single\natomic operation.\n",{"id":54,"difficulty":30,"q":55,"a":56},"ci-cd-github-actions","How do you set up a CI\u002FCD pipeline for a .NET application using GitHub Actions?","GitHub Actions provides first-class .NET support via `actions\u002Fsetup-dotnet`.\nA typical pipeline builds, tests, and deploys the application on every push\nto main.\n\n```yaml\n# .github\u002Fworkflows\u002Fdeploy.yml\nname: Build and Deploy\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  build-and-test:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions\u002Fcheckout@v4\n\n    - name: Setup .NET\n      uses: actions\u002Fsetup-dotnet@v4\n      with:\n        dotnet-version: '8.0.x'\n\n    - name: Restore dependencies\n      run: dotnet restore\n\n    - name: Build\n      run: dotnet build --no-restore -c Release\n\n    - name: Test\n      run: dotnet test --no-build -c Release \\\n           --collect:\"XPlat Code Coverage\" \\\n           --results-directory .\u002Fcoverage\n\n    - name: Publish coverage\n      uses: codecov\u002Fcodecov-action@v4\n      with:\n        directory: .\u002Fcoverage\n\n    - name: Publish\n      run: dotnet publish src\u002FMyApp\u002FMyApp.csproj \\\n           -c Release -o .\u002Fpublish\n\n    - name: Upload artifact\n      uses: actions\u002Fupload-artifact@v4\n      with:\n        name: publish\n        path: .\u002Fpublish\n\n  deploy:\n    needs: build-and-test\n    runs-on: ubuntu-latest\n    if: github.ref == 'refs\u002Fheads\u002Fmain'  # only deploy on main push\n    environment: production               # requires manual approval if configured\n\n    steps:\n    - uses: actions\u002Fdownload-artifact@v4\n      with:\n        name: publish\n        path: .\u002Fpublish\n\n    - name: Deploy to Azure\n      uses: azure\u002Fwebapps-deploy@v3\n      with:\n        app-name: myapp-prod\n        publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}\n        package: .\u002Fpublish\n```\n\n**Rule of thumb:** Separate `build-and-test` and `deploy` into two jobs with\n`needs`. This ensures that tests pass before deployment runs, and the deploy\njob can require manual approval for production via GitHub Environments.\n",{"id":58,"difficulty":30,"q":59,"a":60},"configuration-secrets","How do you manage secrets securely in a .NET production deployment?","Never store secrets in source code or committed configuration files. .NET has\nseveral mechanisms for injecting secrets at runtime.\n\n```csharp\n\u002F\u002F Development: dotnet user-secrets (per-developer, never committed)\n\u002F\u002F dotnet user-secrets init\n\u002F\u002F dotnet user-secrets set \"ConnectionStrings:Default\" \"Server=localhost...\"\n\u002F\u002F Stored in ~\u002F.microsoft\u002Fusersecrets\u002F\u003Cproject-guid>\u002Fsecrets.json\n\n\u002F\u002F Production option 1: environment variables (simplest)\n\u002F\u002F Set in the OS, Docker, or Kubernetes:\n\u002F\u002F CONNECTIONSTRINGS__DEFAULT=Server=prod-db;...\n\u002F\u002F (double underscore maps to colon in .NET config)\n\n\u002F\u002F Production option 2: Azure Key Vault (recommended for Azure deployments)\n\u002F\u002F dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets\nbuilder.Configuration.AddAzureKeyVault(\n    new Uri($\"https:\u002F\u002F{vaultName}.vault.azure.net\u002F\"),\n    new DefaultAzureCredential()); \u002F\u002F uses Managed Identity in Azure, dev credentials locally\n\n\u002F\u002F Production option 3: Kubernetes secrets\n\u002F\u002F kubectl create secret generic myapp-secrets \\\n\u002F\u002F --from-literal=ConnectionStrings__Default=\"Server=prod-db;...\"\n\u002F\u002F Mounted as environment variables in the pod spec:\n\u002F\u002F env:\n\u002F\u002F - name: ConnectionStrings__Default\n\u002F\u002F valueFrom:\n\u002F\u002F secretKeyRef:\n\u002F\u002F name: myapp-secrets\n\u002F\u002F key: ConnectionStrings__Default\n\n\u002F\u002F Validate required configuration at startup — fail fast:\nbuilder.Services.AddOptions\u003CDatabaseOptions>()\n    .BindConfiguration(\"Database\")\n    .ValidateDataAnnotations()\n    .ValidateOnStart(); \u002F\u002F throw on startup if required fields are missing\n```\n\n**Rule of thumb:** Always use `ValidateOnStart()` on critical configuration.\nA startup crash with a clear message (\"ConnectionString is required\") is far\nbetter than a runtime NullReferenceException discovered under load.\n",{"id":62,"difficulty":14,"q":63,"a":64},"performance-profiling","What tools do you use to profile and diagnose performance issues in a .NET application?",".NET ships several built-in diagnostic tools that work on Linux and Windows\nwithout attaching a GUI profiler to production.\n\n```bash\n# dotnet-trace — capture a CPU and allocation trace:\ndotnet tool install --global dotnet-trace\ndotnet-trace collect --process-id \u003Cpid> --duration 00:00:30 \\\n    --profile cpu-sampling\n\n# Analyze with PerfView or SpeedScope (speedscope.app):\ndotnet-trace convert trace.nettrace --format Speedscope\n\n# dotnet-dump — capture and analyze a memory dump:\ndotnet tool install --global dotnet-dump\ndotnet-dump collect --process-id \u003Cpid>\ndotnet-dump analyze core_20260623.dump\n> dumpheap -stat   # top types by allocation count\n> gcroot \u003Caddress> # find what's keeping an object alive\n\n# dotnet-counters — live metrics in the terminal:\ndotnet tool install --global dotnet-counters\ndotnet-counters monitor --process-id \u003Cpid> \\\n    System.Runtime Microsoft.AspNetCore.Hosting\n\n# Outputs real-time:\n# [System.Runtime]\n#     GC Heap Size (MB)          87\n#     Gen 0 GC Count             42\n#     ThreadPool Queue Length     0\n#     CPU Usage (%)              12\n```\n\n```csharp\n\u002F\u002F In code — BenchmarkDotNet for micro-benchmarks:\n[MemoryDiagnoser]\npublic class StringBenchmarks\n{\n    [Benchmark(Baseline = true)]\n    public string Interpolation() => $\"Hello {name}!\";\n\n    [Benchmark]\n    public string SpanBased()     =>\n        string.Create(6 + name.Length, name, (buf, n) =>\n        {\n            \"Hello \".AsSpan().CopyTo(buf);\n            n.AsSpan().CopyTo(buf[6..]);\n            buf[^1] = '!';\n        });\n}\n```\n\n**Rule of thumb:** Profile first, optimize second. Use `dotnet-counters` to\nspot elevated GC pressure or thread pool starvation before reaching for a\nfull trace. Most .NET performance problems are GC allocation issues, not\nalgorithmic complexity — `[MemoryDiagnoser]` in BenchmarkDotNet reveals\nallocations per operation.\n",{"id":66,"difficulty":30,"q":67,"a":68},"deployment-checklist","What is the production deployment checklist for an ASP.NET Core application?","Shipping to production involves configuration, observability, security hardening,\nand infrastructure concerns beyond just running `dotnet publish`.\n\n```csharp\n\u002F\u002F 1. Environment is set to Production:\n\u002F\u002F ASPNETCORE_ENVIRONMENT=Production\n\u002F\u002F (disables developer exception pages, enables compressed responses)\n\n\u002F\u002F 2. Secrets are in environment variables or Key Vault — NOT in committed files\n\n\u002F\u002F 3. HTTPS enforced:\napp.UseHttpsRedirection();\napp.UseHsts(); \u002F\u002F sends Strict-Transport-Security header\n\n\u002F\u002F 4. Security headers:\napp.Use(async (ctx, next) =>\n{\n    ctx.Response.Headers.Append(\"X-Content-Type-Options\", \"nosniff\");\n    ctx.Response.Headers.Append(\"X-Frame-Options\",        \"DENY\");\n    ctx.Response.Headers.Append(\"Referrer-Policy\",        \"strict-origin-when-cross-origin\");\n    await next();\n});\n\n\u002F\u002F 5. Response compression (saves bandwidth):\nbuilder.Services.AddResponseCompression(opts =>\n    opts.EnableForHttps = true);\napp.UseResponseCompression();\n\n\u002F\u002F 6. Health check endpoints registered and tested (see health-probe-kubernetes)\n\n\u002F\u002F 7. Structured logging to a central sink (Seq, Application Insights, ELK)\n\n\u002F\u002F 8. Database migrations run before startup (not during startup in production):\n\u002F\u002F Run as a separate job\u002Finit container:\n\u002F\u002F dotnet ef database update --project MyApp.Data\n\n\u002F\u002F 9. Connection pool tuning:\n\u002F\u002F \"MaxPoolSize=200;Min Pool Size=10;Connection Timeout=30\"\n\n\u002F\u002F 10. Data protection key ring persisted (ASP.NET Core cookie\u002FJWT signing keys):\nbuilder.Services.AddDataProtection()\n    .PersistKeysToAzureBlobStorage(blobClient)\n    .ProtectKeysWithAzureKeyVault(keyIdentifier, credential);\n```\n\n**Rule of thumb:** The most common production bugs are: wrong environment name\n(still `Development`), secrets not injected, data protection keys not shared\nbetween instances (cookie auth breaks), and migrations not run. Verify all\nfour before every first-time production deployment.\n",{"id":70,"difficulty":30,"q":71,"a":72},"blue-green-rolling","What are blue-green and rolling deployment strategies, and how do you implement them for .NET apps?","**Blue-green deployment** runs two identical production environments (blue and\ngreen). Traffic is switched from the current (blue) to the new (green) in a\nsingle atomic step. **Rolling deployment** gradually replaces old instances\nwith new ones, keeping some capacity available throughout.\n\n```yaml\n# Kubernetes rolling deployment (default strategy):\nspec:\n  replicas: 4\n  strategy:\n    type: RollingUpdate\n    rollingUpdate:\n      maxUnavailable: 1   # at most 1 pod down at a time\n      maxSurge: 1         # at most 1 extra pod during rollout\n  template:\n    spec:\n      containers:\n      - name: myapp\n        image: myapp:v2   # update this field to trigger a rollout\n```\n\n```bash\n# Trigger a rolling update by updating the image:\nkubectl set image deployment\u002Fmyapp myapp=myapp:v2\n\n# Monitor rollout status:\nkubectl rollout status deployment\u002Fmyapp\n\n# Roll back if the new version is unhealthy:\nkubectl rollout undo deployment\u002Fmyapp\n```\n\n```csharp\n\u002F\u002F Blue-green via Azure App Service deployment slots:\n\u002F\u002F 1. Deploy new version to the \"staging\" slot (pre-warmed, no traffic):\n\u002F\u002F    az webapp deployment source config-zip --slot staging ...\n\n\u002F\u002F 2. Swap slots atomically — staging becomes production:\n\u002F\u002F    az webapp deployment slot swap --slot staging --target-slot production\n\n\u002F\u002F 3. If issues arise, swap back in seconds:\n\u002F\u002F    az webapp deployment slot swap --slot production --target-slot staging\n\n\u002F\u002F Slot swap warm-up — ASP.NET Core startup hooks:\nbuilder.Services.Configure\u003CIISServerOptions>(opts =>\n    opts.AutomaticAuthentication = false);\n\n\u002F\u002F The slot swap waits for the app to return 200 on \u002Fhealth\u002Fready\n\u002F\u002F before completing the swap:\napp.MapHealthChecks(\"\u002Fhealth\u002Fready\");\n```\n\n**Rule of thumb:** Use rolling updates in Kubernetes for normal deploys —\nthey are zero-downtime by default. Use blue-green (deployment slots or a\nsecond Kubernetes deployment) when you need an instant rollback path with\nno traffic impact.\n",{"id":74,"difficulty":30,"q":75,"a":76},"ef-migrations-cicd","How should you run Entity Framework Core migrations in a CI\u002FCD pipeline?","Running `database update` inside the application startup (`DbContext.Migrate()`)\nis risky in multi-instance deployments because all instances race to apply the\nsame migration simultaneously. The safer pattern is to apply migrations as a\ndedicated step before the application starts.\n\n```csharp\n\u002F\u002F Avoid this in production — causes races with multiple instances:\n\u002F\u002F app.Services.GetRequiredService\u003CAppDbContext>().Database.Migrate();\n\n\u002F\u002F Safe pattern 1: migration as a separate CLI step (GitHub Actions \u002F Azure Pipelines):\n\u002F\u002F dotnet ef database update --project MyApp.Data --startup-project MyApp\n\u002F\u002F Run this AFTER the new binaries are deployed but BEFORE traffic is switched.\n\n\u002F\u002F Safe pattern 2: dedicated migrator project \u002F job:\n\u002F\u002F Create a console app that only runs migrations, then exits:\nvar app = Host.CreateDefaultBuilder(args)\n    .ConfigureServices((ctx, services) =>\n        services.AddDbContext\u003CAppDbContext>(opts =>\n            opts.UseNpgsql(ctx.Configuration.GetConnectionString(\"Default\"))))\n    .Build();\n\nawait using var scope = app.Services.CreateAsyncScope();\nvar db = scope.ServiceProvider.GetRequiredService\u003CAppDbContext>();\nawait db.Database.MigrateAsync();\n\u002F\u002F Process exits here — used as a Kubernetes init container\n```\n\n```yaml\n# Kubernetes init container — runs migrations before the app pod starts:\nspec:\n  initContainers:\n  - name: db-migrate\n    image: myapp:v2\n    command: [\"dotnet\", \"MyApp.Migrator.dll\"]\n    env:\n    - name: ConnectionStrings__Default\n      valueFrom:\n        secretKeyRef:\n          name: myapp-secrets\n          key: db-connection\n  containers:\n  - name: myapp\n    image: myapp:v2\n    # App container starts only after init container succeeds\n```\n\nWrite **backward-compatible migrations**: add nullable columns or columns with\ndefaults first, deploy the app, then tighten constraints in a follow-up migration.\nThis keeps old and new code running against the same schema simultaneously.\n\n**Rule of thumb:** Never call `Migrate()` in `Program.cs` in production. Run\nmigrations as an init container or a pipeline step. If the migration fails, the\napp pods should not start — this prevents serving traffic against a broken schema.\n",{"id":78,"difficulty":14,"q":79,"a":80},"data-protection-multiinstance","Why does ASP.NET Core Data Protection need special configuration in multi-instance deployments?","**Data Protection** generates the keys used to encrypt cookies, anti-forgery\ntokens, and TempData. By default keys are stored in memory and are unique per\nprocess. In a multi-instance deployment, instance A cannot decrypt a cookie\nencrypted by instance B, causing authentication failures and anti-forgery errors.\n\n```csharp\n\u002F\u002F Problem — default in-memory keys: each pod has different keys\n\u002F\u002F User authenticates on pod A, next request goes to pod B → 401 or CSRF failure\n\n\u002F\u002F Solution: share keys across all instances via a common store + key ring protection\n\n\u002F\u002F Option 1: Azure Blob Storage + Azure Key Vault (recommended for Azure):\n\u002F\u002F dotnet add package Azure.Extensions.AspNetCore.DataProtection.Blobs\n\u002F\u002F dotnet add package Azure.Extensions.AspNetCore.DataProtection.Keys\nbuilder.Services.AddDataProtection()\n    .PersistKeysToAzureBlobStorage(\n        new Uri(\"https:\u002F\u002Fmyaccount.blob.core.windows.net\u002Fkeys\u002Fdata-protection.xml\"),\n        new DefaultAzureCredential())\n    .ProtectKeysWithAzureKeyVault(\n        new Uri(\"https:\u002F\u002Fmyvault.vault.azure.net\u002Fkeys\u002Fdata-protection-key\"),\n        new DefaultAzureCredential())\n    .SetApplicationName(\"myapp\"); \u002F\u002F isolates keys from other apps in the same store\n\n\u002F\u002F Option 2: File system share (shared NFS \u002F Azure Files):\nbuilder.Services.AddDataProtection()\n    .PersistKeysToFileSystem(new DirectoryInfo(\"\u002Fmnt\u002Fshared\u002Fkeys\"))\n    .ProtectKeysWithCertificate(certificate);\n\n\u002F\u002F Option 3: Redis (StackExchange.Redis):\n\u002F\u002F dotnet add package Microsoft.AspNetCore.DataProtection.StackExchangeRedis\nbuilder.Services.AddDataProtection()\n    .PersistKeysToStackExchangeRedis(redisConnection, \"DataProtection-Keys\")\n    .SetApplicationName(\"myapp\");\n\n\u002F\u002F Option 4: SQL Server \u002F EF Core:\n\u002F\u002F dotnet add package Microsoft.AspNetCore.DataProtection.EntityFrameworkCore\nbuilder.Services.AddDataProtection()\n    .PersistKeysToDbContext\u003CAppDbContext>();\n```\n\nKey ring settings to review:\n- `SetApplicationName` — required when multiple apps share the same key store\n- `SetDefaultKeyLifetime` — keys rotate every 90 days by default\n- `DisableAutomaticKeyGeneration` — use only in secondary read-only instances\n\n**Rule of thumb:** Always configure a shared key store when running more than\none instance of an ASP.NET Core app. The most common symptom of missing key\nsharing is intermittent 400 errors on form submissions (anti-forgery) or users\nbeing logged out randomly (cookie decryption fails).\n",{"id":82,"difficulty":30,"q":83,"a":84},"container-resource-limits","How do resource limits in Docker and Kubernetes affect .NET runtime behaviour?","The .NET runtime reads CPU and memory limits from cgroup information set by\nDocker or Kubernetes. Misconfigured limits cause GC thrashing, thread pool\nstarvation, or OOMKilled pods.\n\n```yaml\n# Kubernetes resource requests and limits:\nresources:\n  requests:\n    memory: \"256Mi\"   # minimum guaranteed — used for scheduling\n    cpu: \"250m\"       # 0.25 cores guaranteed\n  limits:\n    memory: \"512Mi\"   # hard cap — OOMKill if exceeded\n    cpu: \"1000m\"      # 1 core max (throttled, not killed)\n```\n\n```csharp\n\u002F\u002F .NET automatically sizes GC and thread pool based on cgroup limits\n\u002F\u002F (requires .NET 3.0+ with cgroup v1, .NET 6+ for cgroup v2):\n\n\u002F\u002F Verify the runtime sees correct limits at startup:\nvar cpuCount    = Environment.ProcessorCount;  \u002F\u002F reflects cgroup CPU quota\nvar gcHeapBytes = GC.GetGCMemoryInfo().TotalAvailableMemoryBytes; \u002F\u002F reflects limit\n\n_logger.LogInformation(\"CPU count: {Cpu}, GC heap limit: {Heap} MB\",\n    cpuCount, gcHeapBytes \u002F 1024 \u002F 1024);\n\n\u002F\u002F Server GC (default) — one heap per CPU core; bad with low limits:\n\u002F\u002F With 0.25 cores, Server GC still allocates heaps based on physical cores.\n\u002F\u002F Switch to Workstation GC for low-CPU containers:\n\u002F\u002F In .csproj:\n\u002F\u002F \u003CServerGarbageCollection>false\u003C\u002FServerGarbageCollection>\n\u002F\u002F Or via runtimeconfig.json:\n\u002F\u002F { \"configProperties\": { \"System.GC.Server\": false } }\n\n\u002F\u002F Thread pool: defaults to min threads = CPU count.\n\u002F\u002F With 0.25 vCPU, min threads = 1 by default. Tune if needed:\nThreadPool.SetMinThreads(workerThreads: 4, completionPortThreads: 4);\n```\n\nCommon pitfalls:\n- **No memory limit** — GC uses host total RAM, OOMKilled when the pod exceeds\n  the node's available memory\n- **CPU throttling** — `cpu: limits: 250m` means the container is throttled when\n  it exceeds 250 ms of CPU per 1000 ms; causes latency spikes, not crashes\n- **Server GC + low CPU** — Server GC creates one thread per logical core;\n  with many small-CPU containers this is wasteful\n\n**Rule of thumb:** Set `memory limits` to 1.5–2x the normal heap size observed\nin `dotnet-counters`. Set CPU limits to at least 1 core for latency-sensitive\nservices — CPU throttling introduces tail-latency spikes that are hard to\ndiagnose.\n",15,null,{"description":11},"Deployment interview questions — dotnet publish modes, Kestrel, Docker multi-stage builds, Kubernetes health probes, graceful shutdown, and CI\u002FCD.","dotnet\u002Fperformance-deployment\u002Fdeployment","Performance & Deployment","performance-deployment","2026-06-23","ongyx4Vy8GfP4eZ8R4DkatFLI6BAXD0mU6E0RuP9sE4",[95,99,102],{"subtopic":96,"path":97,"order":98},"Caching","\u002Fdotnet\u002Fperformance-deployment\u002Fcaching",1,{"subtopic":100,"path":101,"order":12},"Logging & Monitoring","\u002Fdotnet\u002Fperformance-deployment\u002Flogging-monitoring",{"subtopic":6,"path":21,"order":20},{"path":104,"title":105},"\u002Fblog\u002Fdotnet-deployment","Deploying ASP.NET Core to Docker, Kubernetes, and Azure",1782244120233]