Retour aux articles

Déploiement d’une Web App .NET sur SmarterASP avec GitHub (CI/CD pas à pas)

Déploiement d’une Web App .NET sur SmarterASP avec GitHub (CI/CD pas à pas)

Vous utilisez GitHub pour versionner votre app ASP.NET Core ou Blazor, et vous hébergez sur SmarterASP.NET ? Ce guide vous montre comment automatiser entièrement vos déploiements avec GitHub Actions, sans quitter l'écosystème GitHub, sans outil tiers, et sans copier un seul fichier à la main.

TL;DR

  • GitHub Actions orchestre le build et le déploiement depuis votre dépôt, sur un runner Windows (nécessaire pour MSDeploy).
  • SmarterASP.NET expose un endpoint WebDeploy (MSDeploy) sur le port 8172, on s'en sert directement.
  • Les secrets (credentials, mot de passe) sont stockés dans les GitHub Secrets, les variables non-sensibles dans les GitHub Variables.
  • Des règles -skip protègent vos fichiers critiques côté serveur (certificats Let's Encrypt, sitemaps…).

GitHub Actions vs Azure DevOps : lequel choisir ?

Si votre code est déjà sur GitHub, GitHub Actions est le choix naturel : tout est au même endroit, la configuration est dans le dépôt, et le modèle gratuit est généreux (2 000 minutes/mois pour les dépôts privés). Azure DevOps reste pertinent si vous avez déjà un écosystème Microsoft (boards, artifacts, test plans), mais pour un projet .NET hébergé sur SmarterASP.NET, GitHub Actions fait le travail sans friction.

La logique de déploiement est identique dans les deux cas : dotnet publish produit un dossier, msdeploy.exe le synchronise avec le serveur. Seule la syntaxe du fichier de configuration change.

ℹ Pré-requis
Vous avez besoin d'un compte GitHub, d'un dépôt contenant votre projet .NET, 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 du workflow

Un workflow GitHub Actions est un fichier YAML placé dans .github/workflows/. Il se compose de jobs, eux-mêmes composés de steps. Ici, on fait tout dans un seul job build_and_deploy pour garder les choses simples, le publish est le résultat direct du build, inutile de stocker un artefact intermédiaire.

push sur main
    └── job: build_and_deploy (windows-latest)
            ├── Checkout
            ├── Setup .NET
            ├── Restore
            ├── Build
            ├── Publish
            ├── WebDeploy → SmarterASP.NET
            └── Warm-up

✓ Pourquoi windows-latest ?
msdeploy.exe est un outil Windows uniquement. Les runners Ubuntu ou macOS ne peuvent pas l'exécuter. GitHub fournit des runners Windows dans son offre gratuite, donc ce n'est pas un problème.


Le workflow complet et commenté

Créez le fichier .github/workflows/deploy.yml dans votre dépôt :

name: Build & Deploy to SmarterASP.NET

# Déclencheurs : push sur main, ou lancement manuel depuis l'interface GitHub
on:
  push:
    branches: [ main ]
  workflow_dispatch:       # Permet de relancer manuellement depuis l'onglet Actions

jobs:
  build_and_deploy:
    runs-on: windows-latest    # Obligatoire : msdeploy.exe n'existe que sur Windows
    environment: prod          # Environnement GitHub (permet les règles de protection et approbations)

    steps:
      # 1. Récupère le code source
      - name: Checkout
        uses: actions/checkout@v4

      # 2. Installe le SDK .NET demandé
      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.0.x'   # Adaptez à votre version cible

      # 3. Restaure les dépendances NuGet
      - name: Restore
        run: dotnet restore src/YourApp/YourApp.csproj

      # 4. Compile en mode Release
      - name: Build
        run: dotnet build src/YourApp/YourApp.csproj --configuration Release --no-restore

      # 5. Publie l'application dans le dossier ./publish
      #    --no-build évite de recompiler ce qui vient d'être compilé
      - name: Publish
        run: dotnet publish src/YourApp/YourApp.csproj --configuration Release --output publish --no-build

      # 6. Déploie via WebDeploy (MSDeploy) vers SmarterASP.NET
      - name: WebDeploy to SmarterASP.NET
        shell: pwsh
        env:
          # Variables non-sensibles : stockées dans GitHub Variables (Settings → Variables)
          WEBSITE_NAME:          ${{ vars.WEBDEPLOY_WEBSITE_NAME }}
          SERVER_COMPUTER_NAME:  ${{ vars.WEBDEPLOY_SERVER_URL }}
          SERVER_USERNAME:       ${{ vars.WEBDEPLOY_USERNAME }}
          PARAM_EXCLUDED_FILES:  ${{ vars.EXCLUDED_FILES }}
          PRESERVE_REMOTE_FILES: ${{ vars.PRESERVE_REMOTE_FILES }}
          # Secret sensible : stocké dans GitHub Secrets (Settings → Secrets)
          SERVER_PASSWORD:       ${{ secrets.WEBDEPLOY_PASSWORD }}
        run: |
          # Recherche msdeploy aux deux emplacements standards des runners Windows
          $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 ce runner" }
          Write-Host "msdeploy trouvé : $msdeploy"
          Write-Host "Déploiement vers : $env:WEBSITE_NAME"

          $sourcePath = "$PWD\publish"

          $msdeployArgs = @(
            "-verb:sync",
            "-source:contentPath=$sourcePath",
            "-dest:contentPath=$env:WEBSITE_NAME,computerName=$env:SERVER_COMPUTER_NAME,userName=$env:SERVER_USERNAME,password=$env:SERVER_PASSWORD,authtype=Basic,includeAcls=False",
            "-allowUntrusted",                  # Utile si la chaîne de certificat pose problème sur le runner
            "-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 ($env:PRESERVE_REMOTE_FILES -eq "true") {
            $msdeployArgs += "-enableRule:DoNotDeleteRule"
          }

          # --- Exclusions personnalisées (via variable EXCLUDED_FILES) ---
          $env: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 (dossier ACME challenge)
          $msdeployArgs += "-skip:skipAction=Delete,objectName=dirPath,absolutePath=(?i).*\\\.well-known.*"

          # Sitemaps générés dynamiquement par l'application au runtime
          $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

          # Vérifie le code de sortie PowerShell ne propage pas toujours les erreurs automatiquement
          if ($LASTEXITCODE -ne 0) {
            Write-Error "msdeploy a échoué avec le code $LASTEXITCODE"
            exit $LASTEXITCODE
          }

      # 7. Warm-up : envoie une requête HTTP pour sortir l'app de sa veille au démarrage
      - name: Warm-up application
        shell: pwsh
        continue-on-error: true    # Un warm-up raté ne doit pas invalider un déploiement réussi
        run: |
          $url = "${{ vars.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 }
            }
          }

Variables et secrets à configurer dans GitHub

Rendez-vous dans Settings → Secrets and variables → Actions de votre dépôt. GitHub distingue deux catégories :

  • Secrets : chiffrés, jamais affichés dans les logs, accessibles via ${{ secrets.NOM }}
  • Variables : en clair dans l'interface, accessibles via ${{ vars.NOM }}
Nom Type Exemple de valeur
WEBDEPLOY_WEBSITE_NAME Variable monsite.com
WEBDEPLOY_SERVER_URL Variable https://win1234.site4now.net:8172/msdeploy.axd?site=monsite.com
WEBDEPLOY_USERNAME Variable votre-login-smarterasp
WEBDEPLOY_PASSWORD Secret •••••••••
WEBSITE_URL Variable https://monsite.com
EXCLUDED_FILES Variable appsettings.Production.json;Web.config
PRESERVE_REMOTE_FILES Variable false

✓ Conseil
Retrouvez votre WEBDEPLOY_SERVER_URL dans le panneau de contrôle SmarterASP.NET → Publish your website → onglet Connection. Il commence toujours par https://winXXXX.site4now.net:8172.


L'environnement prod et les règles de protection

Le workflow référence environment: prod. Pour créer cet environnement dans GitHub, allez dans Settings → Environments → New environment, nommez-le prod.

L'intérêt ? Vous pouvez y ajouter :

  • Des reviewers requis, un collaborateur doit approuver avant que le déploiement parte.
  • Un délai d'attente, par exemple 5 minutes entre le push et le déploiement effectif.
  • Des branches autorisées, seule la branche main peut déclencher un déploiement vers prod.

C'est l'équivalent des approbations dans Azure DevOps Environments, directement intégré à GitHub.


Étape par étape : mettre ça en place

  1. Activez WebDeploy dans le panneau SmarterASP.NET. Vérifiez que le port 8172 est accessible depuis l'extérieur.

  2. Créez le fichier .github/workflows/deploy.yml à la racine de votre dépôt. Copiez le YAML ci-dessus et adaptez le chemin vers votre .csproj et la version .NET.

  3. Ajoutez les secrets et variables dans Settings → Secrets and variables → Actions de votre dépôt GitHub.

  4. Créez l'environnement prod dans Settings → Environments. Ajoutez une règle de protection si vous travaillez en équipe.

  5. Poussez sur main et observez l'onglet Actions de votre dépôt. Chaque step affiche ses logs en temps réel.

  6. En cas d'échec, cliquez sur le step incriminé pour voir le message d'erreur. msdeploy est généralement très explicite (mauvais credentials, port fermé, etc.).


Les pièges courants

« msdeploy introuvable »

Les runners windows-latest de GitHub incluent Web Deploy V3. Le script cherche aux deux emplacements classiques. Si ça échoue, ajoutez un step d'installation avant le déploiement :

- name: Install WebDeploy
  run: choco install webdeploy -y
  shell: pwsh

Mon appsettings.Production.json est écrasé

Ajoutez-le dans la variable EXCLUDED_FILES : appsettings.Production.json. Le script PowerShell le transforme en règle -skip qui protège le fichier côté serveur à chaque déploiement.

⚠ Attention
Ne versionnez jamais vos secrets de production dans appsettings.json. Utilisez appsettings.Production.json hors dépôt, ou GitHub Secrets + injection via les variables d'environnement au runtime.

Le warm-up échoue avec une 302 ou une erreur SSL

Normal si votre site redirige HTTP → HTTPS. Le continue-on-error: true sur le step warm-up fait que GitHub ne considère pas ça comme un échec du workflow. Le déploiement est bien terminé.

Différence avec Azure DevOps : pas d'artefact intermédiaire

Dans la pipeline Azure DevOps de l'article précédent, on publiait un artefact entre le stage Build et le stage Deploy. Ici, comme tout se passe dans un seul job, le dossier ./publish est directement accessible au step suivant. C'est plus simple, mais ça signifie aussi qu'on ne peut pas réutiliser cet artefact pour un second environnement sans restructurer le workflow en plusieurs jobs.


Aller plus loin

Séparer build et deploy en deux jobs

Si vous voulez ajouter des tests automatisés ou déployer vers plusieurs environnements depuis le même artefact, séparez le workflow en deux jobs : un job build qui uploade un artefact via actions/upload-artifact, et un job deploy qui le télécharge via actions/download-artifact. C'est l'équivalent de la structure en stages d'Azure DevOps.

Déclencher uniquement sur certains chemins

Pour éviter de redéployer quand seul un fichier de documentation change :

on:
  push:
    branches: [ main ]
    paths:
      - 'src/**'
      - '.github/workflows/deploy.yml'

Notifications en cas d'échec

Ajoutez un step conditionnel en fin de workflow :

- name: Notifier en cas d'échec
  if: failure()
  run: |
    # Appel webhook Teams, Slack, ou autre
    Invoke-RestMethod -Uri "${{ secrets.SLACK_WEBHOOK }}" -Method Post -Body '{"text":"🚨 Déploiement échoué sur main"}'
  shell: pwsh

✓ Bonne pratique finale
Activez les branch protection rules sur main dans Settings → Branches. Exigez que les pull requests passent par une review avant le merge. Combiné à l'environnement prod avec approbation, vous avez un workflow de déploiement solide sans aucun outil externe.


Conclusion

GitHub Actions rend le déploiement continu vers SmarterASP.NET accessible en moins d'une heure de configuration. Tout vit dans votre dépôt, la configuration est versionnée, et l'historique des déploiements est visible directement dans l'onglet Actions. C'est une alternative sérieuse à Azure DevOps pour les projets qui n'ont pas besoin de l'écosystème complet Microsoft.

Si vous avez déjà lu l'article sur Azure DevOps Pipelines, vous remarquerez que la logique PowerShell/MSDeploy est quasi identique, seul l'habillage YAML change. C'est voulu : une fois que vous maîtrisez WebDeploy, vous pouvez passer d'un outil CI/CD à l'autre sans réapprendre le mécanisme de déploiement.

Des questions sur les migrations EF Core au déploiement, la gestion multi-environnements, ou les GitHub Environments ? Les commentaires sont là pour ça.

Hébergement ASP.NET - SmarterASP.NET

Commentaires (0)

Aucun commentaire pour le moment. Soyez le premier à commenter !

Laisser un commentaire

Votre email ne sera pas publié.
An unhandled error has occurred. Reload 🗙

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please reload the page.