Azure PaaS & CI/CD β€” Hosting, Deploying, and Automating .NET Apps

Learning Context: Part of Phase 2 (Cloud-Native & DevOps) in the Senior .NET Engineer Learning Path 2026.

Prerequisites: NET Internals β€” How It All Works, EF Core & SQL Deep Dive β€” Change Tracker, N+1, and Performance


🧸 Baby Explanation: The Hotel vs Apartment Analogy

When you want to run a .NET application on the internet, you need a computer somewhere that’s always on. There are three ways to do it:

IaaS β€” Renting an Apartment 🏒

You get an empty room (a Virtual Machine). You must:

  • Install Windows/Linux yourself
  • Install .NET runtime yourself
  • Apply security patches yourself
  • Handle backups yourself
  • Set up networking yourself

You have full control, but full responsibility.

PaaS β€” Staying in a Hotel 🏨

You bring your luggage (your code). The hotel handles:

  • Room cleaning (OS patches)
  • Security (firewalls)
  • Breakfast (.NET runtime)
  • Backup generator (high availability)

You just bring your code. They handle the rest.

SaaS β€” Eating at a Restaurant 🍽️

Everything is done for you. You just use the app (Gmail, Teams, etc.).


Part 1: Azure PaaS Services for .NET

Azure App Service β€” The Main One

This is where most .NET APIs and web apps live. It’s PaaS β€” you deploy your code, and Azure runs it.

What it gives you:

FeatureWhat It Means
Auto-scalingIf traffic spikes, Azure spins up more instances automatically
Staging slotsDeploy to β€œstaging” first, swap to β€œproduction” with zero downtime
Custom domainsapi.christianjeremia.com instead of myapp.azurewebsites.net
SSL/TLSFree HTTPS certificate, auto-renewed
CI/CD integrationConnect to GitHub β€” push code, auto-deploy

How deployment works:

You push code β†’ GitHub β†’ Azure picks it up β†’ Builds β†’ Deploys to App Service

Your code goes into a folder on Azure’s servers, and a process called w3wp.exe (IIS) runs it. But you never see that β€” Azure handles everything.

Azure SQL Database

A managed SQL Server in the cloud. No need to:

  • Install SQL Server
  • Apply security patches
  • Manage backups (Azure does it automatically)

Connection string looks like:

Server=mydb.database.windows.net;Database=MyDb;User Id=admin;Password=...;

Same EF Core code as your local SQL Server β€” just change the connection string.

Azure Service Bus

A message queue β€” services send messages to each other without waiting for a response.

Service A sends "OrderPlaced" β†’ [Azure Service Bus] β†’ Service B receives "OrderPlaced"
Service A continues working immediately       β†’ Service B processes the order

This is what makes microservices work. You used this in your Mango project β€” that’s a real senior-level experience.

Azure Container Apps

The modern way to deploy microservices. You package your app as a Docker container, and Azure runs it.

Your code β†’ Docker image β†’ Container Registry β†’ Azure Container Apps β†’ Running!

No Kubernetes complexity. Azure handles scaling, load balancing, and networking.

Application Insights

Monitoring and diagnostics. It answers questions like:

  • β€œWhy is my API slow today?” πŸ“‰
  • β€œWhich endpoint is failing?” ❌
  • β€œHow many users are active?” πŸ“Š
// Add to Program.cs
builder.Services.AddApplicationInsightsTelemetry();

Then you see everything in the Azure portal β€” response times, failure rates, dependency calls (SQL, HTTP), and live logs.


Part 2: The Deployment Pipeline β€” From Code to Running

When you deploy a .NET app, here’s what happens step by step:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  You push  β”‚ ──▢ β”‚  Build   β”‚ ──▢ β”‚   Test    β”‚ ──▢ β”‚  Deploy to β”‚
β”‚  to GitHub β”‚     β”‚  .NET    β”‚     β”‚  xUnit    β”‚     β”‚  Azure     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Step 1: Build

dotnet restore    # Download NuGet packages
dotnet build      # Compile the code

Step 2: Test

dotnet test       # Run all unit tests

If tests fail β†’ stop deployment. Never deploy broken code.

Step 3: Publish

dotnet publish -c Release -o ./publish

This creates a folder with your compiled app and all its dependencies β€” ready to run.

Step 4: Deploy

Upload the published files to Azure App Service. Azure detects the new files and restarts the app.


Part 3: What is CI/CD?

CI β€” Continuous Integration

Every time you push code, the system:

  1. Downloads your code
  2. Restores packages
  3. Builds the project
  4. Runs all tests

If the build or tests fail, you get an email/Slack notification immediately. You fix it before it reaches production.

Without CI: You write code for 2 weeks, then try to build β†’ 50 errors β†’ panic 😱

With CI: You get errors within 5 minutes of writing them β†’ fix immediately βœ…

CD β€” Continuous Delivery/Deployment

After CI passes, the code is automatically deployed.

  • Continuous Delivery: Code is deployed to staging automatically, but someone must click β€œApprove” before going to production.
  • Continuous Deployment: Code goes all the way to production automatically after tests pass.

Part 4: GitHub Actions β€” How It Works

GitHub Actions is a CI/CD system built into GitHub. You define a pipeline in a YAML file.

The Anatomy of a Pipeline

name: Deploy .NET API
 
# πŸ‘‡ When does this run?
on:
  push:
    branches: [main]
 
# πŸ‘‡ What jobs run?
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    
    # πŸ‘‡ What steps inside the job?
    steps:
      - name: Get code
        uses: actions/checkout@v3
      
      - name: Setup .NET
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: '10.0'
      
      - name: Build
        run: dotnet build --configuration Release
      
      - name: Test
        run: dotnet test --configuration Release --no-build
      
      - name: Publish
        run: dotnet publish -c Release -o ./publish
      
      - name: Deploy to Azure
        uses: azure/webapps-deploy@v2
        with:
          app-name: my-dotnet-api
          publish-profile: ${{ secrets.AZURE_PUBLISH_PROFILE }}

Key Concepts

ConceptWhat It IsAnalogy
Trigger (on:)What starts the pipelineLike setting an alarm
JobA unit of work (build, test, deploy)Like a recipe step
StepOne command inside a jobLike an instruction in that step
Action (uses:)Pre-built reusable stepLike a library function
Runner (runs-on:)The VM that executes the jobLike the kitchen where you cook
SecretEncrypted password/connection stringLike a locked safe in the kitchen

Secrets β€” How to Handle Passwords

Never put passwords in your code. Instead, store them in GitHub Secrets:

GitHub β†’ Settings β†’ Secrets and variables β†’ Actions β†’ New repository secret

Then use them in your pipeline:

run: dotnet run --connection-string "${{ secrets.DB_CONNECTION_STRING }}"

The value is encrypted and never appears in logs.


Part 5: Deployment Slots β€” Zero-Downtime Deployments

This is a senior-level concept. It solves the problem:

β€œHow do I deploy new code without users seeing errors during the restart?”

Without Slots (Downtime ❌)

1. User is using the app β†’ "Working"
2. You deploy new code β†’ App restarts β†’ "Error 503"
3. User sees error β†’ "This site is broken!" 😑
4. App finishes restarting β†’ "Working again"

With Slots (Zero Downtime βœ…)

Production Slot     Staging Slot
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Old version    β”‚  β”‚ New version    β”‚
β”‚ (users here)   β”‚  β”‚ (nobody here)  β”‚
β”‚                β”‚  β”‚ Deploy to HERE β”‚
β”‚  my-api       β”‚  β”‚  my-api-stagingβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚                  β”‚
        β”‚     SWAP!        β”‚
        β–Ό                  β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ New version    β”‚  β”‚ Old version    β”‚
β”‚ (users here)   β”‚  β”‚ (ready to rollback) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The swap is instant. Users never see an error.

Bonus: If the new version has a bug, you can swap back immediately β€” no redeployment needed.


Part 6: Putting It All Together β€” A Real Workflow

Here’s how a professional .NET team deploys:

Developer pushes to GitHub
        β”‚
        β–Ό
GitHub Action triggers
        β”‚
        β–Ό
Step 1: dotnet restore + build
        β”‚
        β–Ό
Step 2: dotnet test (if any fail β†’ stop here!)
        β”‚
        β–Ό
Step 3: dotnet publish
        β”‚
        β–Ό
Step 4: Deploy to Azure App Service (Staging slot)
        β”‚
        β–Ό
Step 5: Run smoke tests on staging URL
        β”‚
        β–Ό
Step 6: Swap staging β†’ production (zero downtime)
        β”‚
        β–Ό
Done! πŸš€

If anything goes wrong at Step 5: swap back to old version immediately.


πŸ“Š Cheat Sheet

ConceptKey Thing to Remember
PaaSBring code, not servers
App ServiceMain .NET hosting β€” auto-scale, slots, HTTPS
Azure SQLManaged SQL Server β€” same EF Core, different connection string
Service BusMessage queue for microservices
CIBuild + test every push
CDAuto-deploy after CI passes
GitHub ActionsPipeline defined in .github/workflows/*.yml
SecretsPasswords stored in GitHub, never in code
Deployment SlotsStaging β†’ Swap β†’ Prod = zero downtime
RollbackSwap back to old slot = instant fix

πŸ§ͺ Build Challenges

Challenge 1: Deploy POS API to Azure

This is the real one. Deploy your POS API to Azure App Service:

  1. Create a free Azure account (if you don’t have one)
  2. Create an App Service via the portal
  3. Deploy your POS API using Git deploy or ZIP deploy
  4. Verify the API works at https://your-api.azurewebsites.net

Challenge 2: Add CI/CD with GitHub Actions

  1. Create .github/workflows/deploy.yml in your POS API repo
  2. Add steps: build β†’ test β†’ publish β†’ deploy
  3. Store the publish profile as a GitHub Secret
  4. Push to main and watch the pipeline run

Challenge 3: Set Up Staging and Production Slots

  1. Add a staging slot to your App Service
  2. Deploy new code to staging
  3. Verify it works on the staging URL
  4. Swap staging β†’ production
  5. Test a rollback by swapping back

Rules:

  • ❌ Don’t ask Jeri for code
  • βœ… Understand each concept before deploying
  • βœ… Show Jeri your running Azure URL
  • βœ… Show Jeri a successful GitHub Action run

Related: