Pipelines Azure DevOps pour .NET
Guide pour créer des pipelines Azure DevOps pour build et déploiement d'applications .NET avec Docker.
📋 Prérequis
Avant de créer votre pipeline, assurez-vous que :
- ✅ La configuration NuGet est en place
- ✅ Les agents Azure DevOps self-hosted sont configurés
- ✅ Le service connection vers Azure Container Registry existe
- ✅ Le repository contient des Dockerfiles pour chaque projet
🏗️ Structure du pipeline
Notre pipeline standard pour .NET suit cette structure :
- Trigger : Déclenché sur les branches
docker/* - Restore : Restauration des packages NuGet
- Build : Compilation de la solution
- Publish : Publication de chaque projet
- Docker Build & Push : Création et push des images Docker
📝 Pipeline de base
Créez un fichier azure-pipelines.yml à la racine de votre repo :
trigger:
batch: true
branches:
include:
- docker/*
name: $(Rev:r)
resources:
- repo: self
variables:
appversion: ${{ replace(variables['Build.SourceBranchName'], 'docker/', '') }}
version: $(appversion).$(Build.BuildNumber)
dockerRegistryServiceConnection: 'VOTRE_SERVICE_CONNECTION_ID'
imageRepository: 'votre-app'
containerRegistry: 'votreregistry.azurecr.io'
stages:
- stage: Build
condition: ${{ contains(variables['Build.SourceBranch'], 'docker/') }}
displayName: Build and push stage
jobs:
- job: Build
displayName: Build
pool:
name: 'jit-azure-devops-agents'
steps:
- task: Bash@3
displayName: 'Set Short Commit ID'
inputs:
targetType: 'inline'
script: |
echo "##vso[task.setvariable variable=ShortCommitId]${BUILD_SOURCEVERSION:0:7}"
- task: UseDotNet@2
displayName: 'Install .NET SDK'
inputs:
packageType: 'sdk'
version: '9.x'
installationPath: $(Agent.ToolsDirectory)/dotnet
- script: dotnet workload restore
displayName: 'Restore workloads'
- task: DotNetCoreCLI@2
displayName: 'Restore NuGet packages'
inputs:
command: 'restore'
projects: '**/*.sln'
- task: DotNetCoreCLI@2
displayName: 'Build solution'
inputs:
command: 'build'
projects: '**/*.sln'
arguments: '--configuration Release'
- task: DotNetCoreCLI@2
displayName: 'Publish VotreProjet'
inputs:
command: 'publish'
publishWebProjects: false
zipAfterPublish: false
modifyOutputPath: false
projects: '**/VotreProjet.csproj'
arguments: '--self-contained true --configuration Release --output $(Build.ArtifactStagingDirectory)/VotreProjet /p:Version=$(appversion) /p:AssemblyVersion=$(version) /p:IncludeSourceRevisionInInformationalVersion=false'
- task: Docker@2
displayName: 'Build Docker image - VotreProjet'
inputs:
containerRegistry: '$(dockerRegistryServiceConnection)'
repository: '$(imageRepository)/votreprojet'
command: 'buildAndPush'
Dockerfile: 'VotreProjet/Dockerfile'
buildContext: '$(Build.ArtifactStagingDirectory)/VotreProjet'
tags: |
$(version)
latest
🎯 Exemple complet : Multi-projets
Pour une application avec plusieurs projets (Server, API FileManager, API LogClient) :
trigger:
batch: true
branches:
include:
- docker/*
name: $(Rev:r)
resources:
- repo: self
variables:
appversion: ${{ replace(variables['Build.SourceBranchName'], 'docker/', '') }}
version: $(appversion).$(Build.BuildNumber)
dockerRegistryServiceConnection: '8d9e5dfc-b2fe-4717-b21d-39ada4afe21f'
imageRepository: 'j-erp'
containerRegistry: 'jeromeit.azurecr.io'
stages:
- stage: Build
condition: ${{ contains(variables['Build.SourceBranch'], 'docker/') }}
displayName: Build and push stage
jobs:
- job: Build
displayName: Build
pool:
name: 'jit-azure-devops-agents'
steps:
- task: Bash@3
name: SetShortCommitId
displayName: 'Set Short Commit ID'
inputs:
targetType: 'inline'
script: |
echo "##vso[task.setvariable variable=ShortCommitId]${BUILD_SOURCEVERSION:0:7}"
- task: UseDotNet@2
name: SetSdkVersion
displayName: 'Set sdk version'
inputs:
packageType: 'sdk'
version: '9.x'
installationPath: $(Agent.ToolsDirectory)/dotnet
- script: |
dotnet workload restore
displayName: 'Restore workloads'
- task: DotNetCoreCLI@2
displayName: 'Dotnet restore'
inputs:
command: 'restore'
projects: 'j-ERP.sln'
- task: DotNetCoreCLI@2
displayName: 'build solution'
inputs:
command: 'build'
projects: 'j-ERP.sln'
arguments: '--configuration Release'
# Project 1: Server
- task: DotNetCoreCLI@2
displayName: 'Publish j-ERP.Server'
inputs:
command: 'publish'
publishWebProjects: false
zipAfterPublish: false
modifyOutputPath: false
projects: '**/j-ERP.Server.csproj'
arguments: '--self-contained true --configuration Release --output $(build.artifactstagingdirectory)/Server /p:Version=$(appversion) /p:AssemblyVersion=$(version) /p:IncludeSourceRevisionInInformationalVersion=false'
- task: Docker@2
displayName: 'Build Docker image for j-ERP.Server'
inputs:
containerRegistry: '$(dockerRegistryServiceConnection)'
repository: '$(imageRepository)/j-erp.server'
command: 'buildAndPush'
Dockerfile: 'j-ERP.Server/Dockerfile'
buildContext: '$(Build.ArtifactStagingDirectory)/Server'
tags: |
$(version)
latest
# Project 2: FileManager
- task: DotNetCoreCLI@2
displayName: 'Publish j-ERP.Api.FileManager'
inputs:
command: 'publish'
publishWebProjects: false
zipAfterPublish: false
modifyOutputPath: false
projects: '**/j-ERP.Api.FileManager.csproj'
arguments: '--self-contained true --configuration Release --output $(build.artifactstagingdirectory)/FileManager /p:Version=$(appversion) /p:AssemblyVersion=$(version) /p:IncludeSourceRevisionInInformationalVersion=false'
- task: Docker@2
displayName: 'Build Docker image for j-ERP.Api.FileManager'
inputs:
containerRegistry: '$(dockerRegistryServiceConnection)'
repository: '$(imageRepository)/j-erp.api.filemanager'
command: 'buildAndPush'
Dockerfile: 'j-ERP.Api.FileManager/Dockerfile'
buildContext: '$(Build.ArtifactStagingDirectory)/FileManager'
tags: |
$(version)
latest
# Project 3: LogClient
- task: DotNetCoreCLI@2
displayName: 'Publish j-ERP.Api.LogClient'
inputs:
command: 'publish'
publishWebProjects: false
zipAfterPublish: false
modifyOutputPath: false
projects: '**/j-ERP.Api.LogClient.csproj'
arguments: '--self-contained true --configuration Release --output $(build.artifactstagingdirectory)/LogClient /p:Version=$(appversion) /p:AssemblyVersion=$(version) /p:IncludeSourceRevisionInInformationalVersion=false'
- task: Docker@2
displayName: 'Build Docker image for j-ERP.Api.LogClient'
inputs:
containerRegistry: '$(dockerRegistryServiceConnection)'
repository: '$(imageRepository)/j-erp.api.logclient'
command: 'buildAndPush'
Dockerfile: 'j-ERP.Api.LogClient/Dockerfile'
buildContext: '$(Build.ArtifactStagingDirectory)/LogClient'
tags: |
$(version)
latest
🔧 Configuration du pool d'agents
Utiliser un pool self-hosted
pool:
name: 'jit-azure-devops-agents'
Utiliser un agent Microsoft-hosted
pool:
vmImage: 'ubuntu-latest'
🐳 Dockerfiles
Chaque projet doit avoir son Dockerfile. Exemple pour une API ASP.NET :
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM base AS final
WORKDIR /app
COPY . .
ENTRYPOINT ["dotnet", "VotreProjet.dll"]
Le Dockerfile utilise le contexte de build $(Build.ArtifactStagingDirectory)/VotreProjet qui contient déjà les fichiers publiés, donc un simple COPY suffit.
📊 Variables importantes
| Variable | Description | Exemple |
|---|---|---|
appversion | Version extraite du nom de branche | 4.1.3 (depuis docker/4.1.3) |
version | Version complète avec build number | 4.1.3.56 |
Build.SourceBranchName | Nom de la branche | docker/4.1.3 |
Build.BuildNumber | Numéro de build incrémental | 56 |
Build.ArtifactStagingDirectory | Répertoire des artifacts | /home/azureuser/azagent/_work/9/a |
🚀 Workflow de release
1. Créer une branche de version
git checkout -b docker/4.1.3
git push origin docker/4.1.3
2. Le pipeline se déclenche automatiquement
Le pipeline :
- Build la solution
- Publie chaque projet
- Crée les images Docker
- Push vers Azure Container Registry avec les tags :
4.1.3.56(version + build number)latest
3. Déployer l'image
docker pull jeromeit.azurecr.io/j-erp/j-erp.server:4.1.3.56
# ou
docker pull jeromeit.azurecr.io/j-erp/j-erp.server:latest
🐛 Troubleshooting
Erreur : No image label found to route agent pool
Symptôme :
##[error]No image label found to route agent pool Azure Pipelines
Cause : Vous utilisez vmImage au lieu de name pour un pool self-hosted.
Solution :
# ❌ Incorrect pour self-hosted
pool:
vmImage: 'jit-azure-devops-agents'
# ✅ Correct pour self-hosted
pool:
name: 'jit-azure-devops-agents'
Erreur NuGet 401/404
Voir la section troubleshooting de la documentation NuGet.
Les images Docker ne se buildent pas
Vérifications :
- Le Dockerfile existe dans le bon répertoire
- Le
buildContextpointe vers le bon dossier - Docker est installé et accessible sur l'agent
# Sur l'agent
docker --version
docker images
📝 Template réutilisable
Vous pouvez créer un template réutilisable pour éviter la duplication :
parameters:
- name: projectName
type: string
- name: projectFile
type: string
- name: dockerRepository
type: string
steps:
- task: DotNetCoreCLI@2
displayName: 'Publish ${{ parameters.projectName }}'
inputs:
command: 'publish'
publishWebProjects: false
zipAfterPublish: false
projects: '**/${{ parameters.projectFile }}'
arguments: '--self-contained true --configuration Release --output $(Build.ArtifactStagingDirectory)/${{ parameters.projectName }}'
- task: Docker@2
displayName: 'Build Docker - ${{ parameters.projectName }}'
inputs:
containerRegistry: '$(dockerRegistryServiceConnection)'
repository: '${{ parameters.dockerRepository }}'
command: 'buildAndPush'
Dockerfile: '${{ parameters.projectName }}/Dockerfile'
buildContext: '$(Build.ArtifactStagingDirectory)/${{ parameters.projectName }}'
tags: |
$(version)
latest
Utilisation :
- template: templates/dotnet-publish-docker.yml
parameters:
projectName: 'j-ERP.Server'
projectFile: 'j-ERP.Server.csproj'
dockerRepository: '$(imageRepository)/j-erp.server'