CI/CD voor .NET op Azure: Van nul tot productie
15 feb 2026
CI/CD opzetten voor een .NET-applicatie op Azure betekent het hele pad van code commit tot productie-deployment automatiseren — build, test en release — met Azure DevOps Pipelines of GitHub Actions. Na vijf jaar bouwen en onderhouden van deze pipelines bij HUSS B.V. kan ik je vertellen dat het verschil tussen een tutorial-pipeline en een productiewaardige pipeline aanzienlijk is. Deze gids laat zien hoe dat verschil eruitziet en hoe je het overbrugt.
Waarom CI/CD belangrijk is voor .NET-projecten
Handmatige deployments zijn een risico. Elke keer dat iemand met de rechtermuisknop op “Publish” klikt in Visual Studio, gok je op consistentie. CI/CD elimineert dat risico door elke deployment identiek, traceerbaar en omkeerbaar te maken. Het dwingt je ook om testbare code te schrijven — want als je pipeline bij elke commit tests draait, moet je ze ook daadwerkelijk schrijven.
Voor .NET op Azure specifiek is de tooling volwassen. Microsoft heeft flink geinvesteerd om dit pad soepel te maken, of je nu kiest voor Azure DevOps of GitHub Actions. De vraag is niet of je moet automatiseren, maar hoe je het goed doet.
Azure DevOps vs GitHub Actions voor .NET
Beide platformen kunnen de klus klaren. De keuze komt meestal neer op de bestaande tooling en voorkeuren van je organisatie, niet op een fundamenteel technisch verschil. Zo vergelijken ze in de praktijk:
| Eigenschap | Azure DevOps Pipelines | GitHub Actions |
|---|---|---|
| YAML pipeline-ondersteuning | Ja | Ja |
| Ingebouwde .NET SDK-taken | Uitgebreide takenbibliotheek (DotNetCoreCLI@2) | Community actions + dotnet CLI direct |
| Azure-integratie | Native service connections | Azure Login action + service principals |
| Deployment slots | Eersteklas swap-taak | Ondersteund via azure/webapps-deploy |
| Artefactbeheer | Azure Artifacts ingebouwd | GitHub Packages of externe feeds |
| Self-hosted agents | Ondersteund | Ondersteund (self-hosted runners) |
| Omgevingen & goedkeuringen | Environments met gates en checks | Environments met protection rules |
| Prijs voor private repos | 1 gratis parallelle job (1800 min/maand) | 2000 min/maand gratis |
| Leercurve | Steiler — meer concepten om te leren | Lager — eenvoudigere YAML-structuur |
Mijn aanbeveling: als je organisatie al Azure DevOps gebruikt voor boards en repos, blijf dan bij Azure DevOps Pipelines. Als je code op GitHub staat, gebruik dan GitHub Actions. Migreren tussen de twee is eenvoudig — de pipeline-logica is vrijwel identiek, alleen de YAML-syntax verschilt.
Hoe je CI/CD opzet voor .NET op Azure
Een productiewaardige pipeline volgt dezelfde kernflow ongeacht het platform: restore, build, test, publish, deploy. Hieronder vind je werkende voorbeelden voor zowel Azure DevOps als GitHub Actions die een .NET 8 webapplicatie deployen naar Azure App Service.
Azure DevOps Pipeline
trigger:
branches:
include:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
buildConfiguration: 'Release'
dotnetVersion: '8.x'
azureSubscription: 'MyAzureServiceConnection'
appName: 'my-app-production'
stages:
- stage: Build
displayName: 'Build & Test'
jobs:
- job: BuildJob
steps:
- task: UseDotNet@2
inputs:
packageType: 'sdk'
version: '$(dotnetVersion)'
- task: DotNetCoreCLI@2
displayName: 'Restore'
inputs:
command: 'restore'
projects: '**/*.csproj'
- task: DotNetCoreCLI@2
displayName: 'Build'
inputs:
command: 'build'
arguments: '--configuration $(buildConfiguration) --no-restore'
- task: DotNetCoreCLI@2
displayName: 'Test'
inputs:
command: 'test'
arguments: '--configuration $(buildConfiguration) --no-build --collect:"XPlat Code Coverage"'
- task: DotNetCoreCLI@2
displayName: 'Publish'
inputs:
command: 'publish'
publishWebProjects: true
arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'
- publish: $(Build.ArtifactStagingDirectory)
artifact: 'webapp'
- stage: DeployStaging
displayName: 'Deploy to Staging'
dependsOn: Build
jobs:
- deployment: DeployStaging
environment: 'staging'
strategy:
runOnce:
deploy:
steps:
- task: AzureWebApp@1
inputs:
azureSubscription: '$(azureSubscription)'
appType: 'webAppLinux'
appName: '$(appName)'
deployToSlotOrASE: true
slotName: 'staging'
package: '$(Pipeline.Workspace)/webapp/**/*.zip'
- stage: DeployProduction
displayName: 'Deploy to Production'
dependsOn: DeployStaging
jobs:
- deployment: DeployProduction
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- task: AzureAppServiceManage@0
inputs:
azureSubscription: '$(azureSubscription)'
action: 'Swap Slots'
webAppName: '$(appName)'
sourceSlot: 'staging'
targetSlot: 'production' GitHub Actions Workflow
name: Build and Deploy
on:
push:
branches: [main]
env:
DOTNET_VERSION: '8.x'
AZURE_WEBAPP_NAME: 'my-app-production'
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Test
run: dotnet test --configuration Release --no-build --verbosity normal
- name: Publish
run: dotnet publish --configuration Release --output ./publish
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: webapp
path: ./publish
deploy-staging:
needs: build-and-test
runs-on: ubuntu-latest
environment: staging
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: webapp
- name: Login to Azure
uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Deploy to staging slot
uses: azure/webapps-deploy@v3
with:
app-name: ${{ env.AZURE_WEBAPP_NAME }}
slot-name: staging
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production
steps:
- name: Login to Azure
uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Swap staging to production
run: |
az webapp deployment slot swap
--name ${{ env.AZURE_WEBAPP_NAME }}
--resource-group my-resource-group
--slot staging
--target-slot production Deployment slots en zero-downtime releases
Deployment slots zijn de allerbelangrijkste feature voor productie-.NET-deployments op Azure App Service. De strategie is simpel: deploy naar een staging slot, controleer of het werkt en swap staging naar productie. De swap is vrijwel direct omdat Azure alleen de virtuele IP-routering omschakelt — je app is al opgewarmd in de staging slot.
Een paar dingen die ik op de harde manier heb geleerd:
- Configureer altijd slot-sticky instellingen. Connection strings en app-instellingen die verschillen tussen staging en productie — zoals databaseverbindingen — moeten als “slot sticky” worden gemarkeerd, zodat ze niet meeverhuizen bij de swap.
- Gebruik health checks. Configureer het App Service health check-endpoint zodat Azure kan verifieren dat je app gezond is voordat de swap wordt voltooid.
- Test het swap-pad, niet alleen de deployment. Ik heb pipelines gezien waar de deploy-naar-slot stap perfect werkte, maar de swap faalde door configuratieverschillen. Automatiseer de volledige flow.
Infrastructure as Code: Provisioning voor deployment
Je pipeline moet er niet vanuit gaan dat de infrastructuur al bestaat. Gebruik Bicep of ARM templates om je Azure-resources te provisioneren als onderdeel van de pipeline — of op z’n minst in een aparte infrastructuur-pipeline die eerst draait.
Bij HUSS gebruikte ik Bicep templates om het App Service Plan, de App Service, deployment slots, Application Insights en Key Vault-referenties te definieren. Dat betekende dat het opzetten van een nieuwe omgeving een enkele pipeline-run was, niet een dag klikken door de Azure-portal.
Een minimaal Bicep-fragment voor een App Service met een staging slot:
resource appService 'Microsoft.Web/sites@2023-01-01' = {
name: appName
location: location
properties: {
serverFarmId: appServicePlan.id
siteConfig: {
netFrameworkVersion: 'v8.0'
}
}
}
resource stagingSlot 'Microsoft.Web/sites/slots@2023-01-01' = {
parent: appService
name: 'staging'
location: location
properties: {
serverFarmId: appServicePlan.id
}
} Veelgemaakte fouten om te vermijden
Je .NET SDK-versie niet vastpinnen. Als je vertrouwt op welke versie dan ook die voorgeinstalleerd is op de build agent, gaan je builds willekeurig kapot als de agent-image wordt bijgewerkt. Specificeer altijd de exacte SDK-versie.
Tests overslaan in de pipeline. Ik snap het — tests vertragen de boel. Maar een pipeline zonder tests is gewoon geautomatiseerd risico. Draai op z’n minst je unit tests. Voeg integratietests tegen een testdatabase toe als je kunt.
Dezelfde service principal voor alles gebruiken. Maak aparte service principals voor staging en productie met passend afgebakende rechten. Least privilege is niet optioneel.
NuGet-packages niet cachen. Beide platformen ondersteunen dependency caching. Gebruik het. Het verkort buildtijden aanzienlijk bij grotere solutions.
Direct naar productie deployen. Zelfs als je een solo-developer bent, gebruik een staging slot. De vijf minuten die het toevoegt aan je pipeline besparen uren aan het debuggen van productie-incidenten.
Veelgestelde vragen
Heb ik Azure DevOps nodig als mijn code op GitHub staat?
Nee. GitHub Actions heeft uitstekende Azure-integratie via officiele actions zoals azure/login en azure/webapps-deploy. Je hebt Azure DevOps alleen nodig als je de andere features wilt, zoals Boards, Test Plans of Azure Artifacts. Voor pure CI/CD is GitHub Actions prima geschikt.
Kan ik .NET Framework (niet .NET Core) deployen met deze pipelines?
Ja, maar met kanttekeningen. .NET Framework vereist Windows build agents (windows-latest in plaats van ubuntu-latest), en je gebruikt MSBuild-taken in plaats van dotnet CLI-commando’s. De algehele pipeline-structuur blijft hetzelfde — build, test, publish, deploy — maar de specifieke commando’s verschillen.
Hoe ga ik om met databasemigraties in de pipeline?
Voer EF Core-migraties uit als een stap tussen deployment en slot swap. Deploy je code naar de staging slot, voer dotnet ef database update uit tegen de staging-database, controleer of alles werkt en swap dan. Voer migraties nooit rechtstreeks uit tegen productie tijdens de swap — doe ze ervoor.
Wat kosten deployment slots op Azure App Service?
Deployment slots zijn beschikbaar vanaf de Standard-tier zonder extra kosten voor de slot zelf. De staging slot verbruikt echter resources van je App Service Plan wanneer deze draait. Je kunt de staging slot stoppen na de swap om die resources vrij te maken als kosten een zorg zijn.
Moet ik YAML pipelines of de klassieke editor gebruiken in Azure DevOps?
Gebruik altijd YAML pipelines. De klassieke editor wordt uitgefaseerd, en YAML pipelines staan in je repository naast je code. Dat betekent dat je pipeline-definitie onder versiebeheer staat, reviewbaar is in pull requests en overdraagbaar is. Er is geen goede reden om de klassieke editor te gebruiken voor nieuwe projecten.