Vous hébergez une app ASP.NET Core ou Blazor sur SmarterASP.NET et vous en avez assez de copier des fichiers à la main via FileZilla ? Ce guide vous explique comment mettre en place une pipeline CI/CD complète avec Azure DevOps qui build, publie et déploie automatiquement votre application à chaque push.
TL;DR
- SmarterASP.NET expose un endpoint WebDeploy (MSDeploy) sur le port 8172.
- Azure DevOps build votre projet avec
dotnet publish, publie un artefact, puis invoquemsdeploy.exedepuis l'agent Windows.- Les secrets (credentials, URL) sont stockés dans les variables secrètes du pipeline, jamais en clair dans le YAML.
- Des règles
-skipprotègent vos fichiers précieux côté serveur (sitemaps, certificats Let's Encrypt…).
Pourquoi WebDeploy plutôt que FTP ?
FTP, c'est bien pour un premier déploiement en développement. Mais pour un projet qui évolue, plusieurs développeurs, branches, environnements, c'est une source de bugs silencieux : fichier oublié, ancienne DLL qui traîne, déploiement partiel après un timeout. WebDeploy (aka MSDeploy) règle ça en faisant une synchronisation différentielle entre votre dossier de publication et le serveur. Il ne transfère que ce qui a changé, et peut supprimer les fichiers obsolètes.
SmarterASP.NET supporte WebDeploy nativement. Leur endpoint ressemble à https://winXXXX.site4now.net:8172/msdeploy.axd. On va s'en servir directement depuis Azure DevOps.
ℹ Pré-requis
Vous avez besoin d'un compte Azure DevOps (gratuit pour les petits projets), d'un projet Git, et de vos identifiants SmarterASP.NET (username / password de votre panneau de contrôle). WebDeploy doit être activé dans les paramètres de votre site.
Pas encore client ? Mon lien partenaire vous donne accès à une offre promotionnelle.
Anatomie de la pipeline
La pipeline se découpe en deux stages : Build et Deploy. Cette séparation est importante : elle vous permet par exemple d'ajouter un stage de tests entre les deux, ou de déployer le même artefact vers plusieurs environnements.
stages:
- stage: Build # dotnet restore → build → publish → artefact
- stage: Deploy # download artefact → msdeploy → warm-up
dependsOn: Build
condition: succeeded()
La pipeline complète et commentée
pool:
vmImage: 'windows-latest' # MSDeploy n'existe que sur Windows
variables:
BuildConfiguration: 'Release'
DotNetVersion: '10.x' # Centralisé ici pour faciliter les mises à jour
ProjectPath: 'src\{lien-vers-votre-csproj}.csproj'
PreserveRemoteFiles: 'false' # true = ne supprime pas les fichiers inconnus
# -------------------------------------------------------
# STAGE 1 - Build
# -------------------------------------------------------
stages:
- stage: Build
displayName: 'Build & Publish'
jobs:
- job: Build
steps:
- task: UseDotNet@2
displayName: 'SDK .NET $(DotNetVersion)'
inputs:
packageType: 'sdk'
version: '$(DotNetVersion)'
- task: DotNetCoreCLI@2
displayName: 'Restore'
inputs:
command: 'restore'
projects: '$(ProjectPath)'
- task: DotNetCoreCLI@2
displayName: 'Build'
inputs:
command: 'build'
projects: '$(ProjectPath)'
arguments: '--configuration $(BuildConfiguration) --no-restore'
- task: DotNetCoreCLI@2
displayName: 'Publish'
inputs:
command: 'publish'
publishWebProjects: false
projects: '$(ProjectPath)'
# --no-build évite de recompiler ce qui vient d'être compilé
arguments: >-
--configuration $(BuildConfiguration)
--output $(Build.ArtifactStagingDirectory)
--no-build
zipAfterPublish: false
# Publie le dossier généré comme artefact nommé "webapp"
- publish: '$(Build.ArtifactStagingDirectory)/webapp'
displayName: 'Publie l''artefact webapp'
artifact: 'webapp'
# -------------------------------------------------------
# STAGE 2 - Deploy
# -------------------------------------------------------
- stage: Deploy
displayName: 'Déploiement WebDeploy → SmarterASP.NET'
dependsOn: Build
condition: succeeded()
jobs:
- deployment: DeployToSmarterASP
environment: prod
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: 'webapp'
- powershell: |
$sourcePath = "$(Pipeline.Workspace)\webapp"
# Recherche msdeploy aux deux emplacements standards des agents MS
$msdeploy = @(
"C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe",
"C:\Program Files (x86)\IIS\Microsoft Web Deploy V3\msdeploy.exe"
) | Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $msdeploy) { throw "msdeploy.exe introuvable sur cet agent" }
Write-Host "msdeploy trouvé : $msdeploy"
$msdeployArgs = @(
'-verb:sync',
"-source:contentPath=""$sourcePath""",
"-dest:contentPath=""$(WEBSITE_NAME)"",computerName=""$(SERVER_COMPUTER_NAME)"",userName=""$(SERVER_USERNAME)"",password=""$(SERVER_PASSWORD)"",authtype=""Basic"",includeAcls=""False""",
'-allowUntrusted',
'-disableLink:AppPoolExtension',
'-disableLink:ContentExtension',
'-disableLink:CertificateExtension',
'-enableRule:AppOffline' # Place un app_offline.htm pendant le déploiement
)
# Option : ne pas supprimer les fichiers inconnus côté serveur
if ("$(PreserveRemoteFiles)".ToLower() -eq "true") {
$msdeployArgs += '-enableRule:DoNotDeleteRule'
}
# --- Exclusions personnalisées (via variable PARAM_EXCLUDED_FILES) ---
"$(PARAM_EXCLUDED_FILES)".Split(";") |
Where-Object { $_.Trim() -ne "" } |
ForEach-Object {
$regex = "(?i).*$([Regex]::Escape($_.Trim()))$"
Write-Host "Exclusion fichier : $regex"
$msdeployArgs += "-skip:skipAction=Delete,objectName=filePath,absolutePath=`"$regex`""
}
# --- Protéger les dossiers critiques côté serveur ---
# Certificats Let's Encrypt (ACME challenge)
$msdeployArgs += "-skip:skipAction=Delete,objectName=dirPath,absolutePath=`"(?i).*\\\.well-known.*`""
# Sitemaps générés dynamiquement
$msdeployArgs += "-skip:skipAction=Delete,objectName=dirPath,absolutePath=`"(?i).*\\wwwroot\\sitemaps(`$|\\.*)`""
$msdeployArgs += "-skip:skipAction=Delete,objectName=filePath,absolutePath=`"(?i).*\\wwwroot\\sitemaps\\.*`""
Write-Host "Lancement de msdeploy..."
& $msdeploy @msdeployArgs
if ($LASTEXITCODE -ne 0) {
Write-Error "msdeploy a échoué avec le code $LASTEXITCODE"
exit $LASTEXITCODE
}
displayName: 'WebDeploy → SmarterASP.NET'
env:
WEBSITE_NAME: '$(WEBSITE_NAME)'
SERVER_COMPUTER_NAME: '$(SERVER_COMPUTER_NAME)'
SERVER_USERNAME: '$(SERVER_USERNAME)'
SERVER_PASSWORD: '$(SERVER_PASSWORD)'
# Warm-up : réveille l'app après le déploiement
- powershell: |
$url = "$(WEBSITE_URL)"
$maxRetries = 3
$retryDelay = 10
Write-Host "Warm-up de $url..."
for ($i = 1; $i -le $maxRetries; $i++) {
try {
$r = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 45
Write-Host "Site répond : $($r.StatusCode) - OK"
break
} catch {
Write-Host "Tentative $i/$maxRetries échouée : $_"
if ($i -lt $maxRetries) { Start-Sleep -Seconds $retryDelay }
}
}
displayName: 'Warm-up application'
continueOnError: true # Un warm-up raté ne doit pas faire échouer le déploiement
Les variables à configurer dans Azure DevOps
Rendez-vous dans Pipelines → votre pipeline → Edit → Variables. Certaines sont publiques, d'autres doivent être marquées comme secret pour ne jamais apparaître dans les logs.
| Variable | Exemple de valeur | Secret ? |
|---|---|---|
WEBSITE_NAME |
monsite.com |
Non |
SERVER_COMPUTER_NAME |
https://win1234.site4now.net:8172/msdeploy.axd?site=monsite.com |
Non |
SERVER_USERNAME |
votre-login-smarterasp |
Oui |
SERVER_PASSWORD |
••••••••• |
Oui |
WEBSITE_URL |
https://monsite.com |
Non |
PARAM_EXCLUDED_FILES |
appsettings.Production.json;Web.config |
Non |
✓ Conseil
Retrouvez votreSERVER_COMPUTER_NAMEdans le panneau de contrôle SmarterASP.NET → Publish your website → onglet Connection. Il commence toujours parhttps://winXXXX.site4now.net:8172.
Étape par étape : mettre ça en place
Activez WebDeploy dans le panneau SmarterASP.NET. Par défaut c'est activé, mais vérifiez que le port 8172 n'est pas bloqué par un firewall de votre côté.
Créez le fichier
azure-pipelines.ymlà la racine de votre dépôt Git. Copiez le YAML ci-dessus et adaptezProjectPathetDotNetVersionà votre projet.Dans Azure DevOps, allez dans Pipelines → New pipeline, pointez sur votre dépôt, sélectionnez Existing Azure Pipelines YAML file.
Ajoutez les variables dans l'interface Azure DevOps (pas dans le YAML !). Cochez Keep this value secret pour le mot de passe et le username.
Créez l'environnement
proddans Pipelines → Environments. Vous pouvez y ajouter une approbation manuelle avant déploiement si vous travaillez en équipe.Lancez un premier run et observez les logs. Si msdeploy échoue, le message d'erreur est généralement explicite (mauvais credentials, port fermé, certificat non approuvé).
Les pièges courants
« msdeploy introuvable »
Les agents windows-latest de Microsoft incluent Web Deploy V3. Le script cherche aux deux emplacements classiques. Si ça échoue malgré tout, ajoutez une étape qui installe WebDeploy via Chocolatey :
choco install webdeploy -y
Certificat SSL non approuvé sur l'agent
Le flag -allowUntrusted dans la commande msdeploy règle ce problème. SmarterASP.NET utilise des certificats valides, mais les agents hébergés peuvent parfois avoir des problèmes de chaîne de confiance.
Mon appsettings.Production.json est écrasé
C'est le problème numéro un en production. Ajoutez ce fichier dans la variable PARAM_EXCLUDED_FILES : le script PowerShell le transforme automatiquement en règle -skip qui empêche msdeploy de le supprimer ou le remplacer.
⚠ Attention
Ne mettez jamais vos secrets de production (ConnectionStrings, clés API…) dansappsettings.jsonversionné. Utilisezappsettings.Production.jsonhors dépôt Git, ou mieux : Azure Key Vault si votre offre SmarterASP.NET le permet.
Le warm-up échoue avec une 302
Normal. Si votre site redirige HTTP → HTTPS, Invoke-WebRequest peut signaler une erreur sur la redirection. Le flag continueOnError: true fait que le pipeline ne considère pas ça comme un échec bloquant. Le site tourne très bien malgré tout.
Mes sitemaps générés dynamiquement disparaissent
Si votre app Blazor écrit des sitemaps dans wwwroot/sitemaps/ au runtime, ces fichiers n'existent pas dans l'artefact de publication. Sans précaution, msdeploy les efface à chaque déploiement. Les deux règles -skip dédiées dans le script protègent ce dossier.
Aller plus loin
Ajouter des tests avant le déploiement
Insérez un job Test entre Build et Deploy, ou ajoutez une étape DotNetCoreCLI@2 avec command: 'test' dans le stage Build. Azure DevOps intègre les résultats de tests directement dans l'interface.
Déployer sur plusieurs environnements
Dupliquez le stage Deploy et nommez-les staging et prod. Utilisez des variable groups différents pour chaque environnement (Library → Variable groups). Ajoutez une approbation manuelle sur l'environnement prod dans Pipelines → Environments.
Notifier en cas d'échec
Azure DevOps peut envoyer des emails ou des messages Teams/Slack en cas d'échec de pipeline. Configurez ça dans Project Settings → Notifications ou via une tâche InvokeRestAPI qui appelle un webhook.
✓ Bonne pratique finale
Déclenchez la pipeline uniquement sur les commits versmainen ajoutant un trigger en haut du YAML :trigger: - mainLes branches de développement peuvent ainsi être poussées librement sans déclencher un déploiement en production.
Conclusion
Mettre en place ce pipeline demande une heure la première fois, mais c'est du temps récupéré à chaque déploiement ensuite. Plus de FileZilla, plus de risque d'oublier un fichier, plus de déploiements à 23h en croisant les doigts. Juste un git push sur main et votre app est en ligne deux minutes plus tard.
La combinaison Azure DevOps + MSDeploy + SmarterASP.NET est un peu moins connue que les grands clouds managés, mais elle fonctionne très bien pour des projets .NET qui n'ont pas besoin (ou pas le budget) d'Azure App Service ou AWS Elastic Beanstalk. Ce guide devrait vous permettre de la mettre en place sans tâtonner.
Si vous avez des questions sur un point spécifique, gestion des migrations EF Core au déploiement, multi-environnements, ou intégration avec Azure Key Vault, les commentaires sont là pour ça.


Commentaires (0)
Aucun commentaire pour le moment. Soyez le premier à commenter !
Laisser un commentaire