What is the best way to set up CI/CD for ASP.NET Core apps targeting Azure App Service using GitHub Actions?

Asked 28 days ago Updated 4 days ago 195 views

0

Automating Deployments to App Service

Manual zip-and-drop deployments do not scale. A GitHub Actions workflow can build, test, and push your ASP.NET Core project straight to an Azure App Service slot, giving you zero-downtime releases.

Workflow Structure

The pipeline needs an Azure login action, a build step that restores NuGet packages and runs your test suite, and a deployment action targeting the correct slot. Environment variables for connection strings should live in the repository secrets, not the workflow file.

# .github/workflows/deploy.yml
name: Deploy to Azure App Service

on:
  push:
    branches: [ main ]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      # Authenticate against Azure using a service principal stored in secrets.
      - name: Azure Login
        uses: azure/login@v1
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      # Restore dependencies and build the web project.
      - name: Setup .NET
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: '8.0.x'
      - run: dotnet restore
      - run: dotnet build --no-restore

      # Publish the app and slot it into the staging environment.
      - name: Deploy to App Service
        uses: Azure/webapps-deploy@v3
        with:
          app-name: 'my-aspnet-app'
          slot-name: 'staging'
          package: 'publish/my-aspnet-app.zip'

Always promote the staging slot to production only after health checks pass. Skipping this step means a broken build can instantly replace live traffic.

1 Answer


0

A solid setup is to build and test on every push to main, publish the app once, then deploy that same artifact to Azure App Service. For authentication, use GitHub Actions OpenID Connect (OIDC) with a federated identity credential in Azure rather than storing a long-lived Azure password or publish profile in GitHub.

Here’s a starting GitHub Actions workflow. Replace the solution and project paths with yours. Add the Azure IDs and resource names as GitHub repository or environment variables: AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID, AZURE_RESOURCE_GROUP, and AZURE_WEBAPP_NAME.

name: ASP.NET Core CI/CD

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      # Install the same .NET SDK version your app targets.
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.0.x'

      - name: Restore dependencies
        run: dotnet restore MyApp.sln

      # Run tests before creating the deployable artifact.
      - name: Test
        run: dotnet test MyApp.sln --configuration Release --no-restore

      # Publish to a clean folder so the ZIP has the right App Service layout.
      - name: Publish
        run: dotnet publish src/MyApp/MyApp.csproj --configuration Release --no-restore --output publish

      - name: Package published output
        run: |
          cd publish
          zip -r ../app.zip .

      # Pass this exact build to the deployment job.
      - uses: actions/upload-artifact@v4
        with:
          name: app-package
          path: app.zip

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment: production
    permissions:
      contents: read
      id-token: write

    steps:
      - uses: actions/download-artifact@v4
        with:
          name: app-package
          path: drop

      # OIDC requires a federated credential configured for this GitHub repository/environment in Azure.
      - uses: azure/login@v2
        with:
          client-id: ${{ vars.AZURE_CLIENT_ID }}
          tenant-id: ${{ vars.AZURE_TENANT_ID }}
          subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}

      - name: Deploy to Azure App Service
        env:
          RESOURCE_GROUP: ${{ vars.AZURE_RESOURCE_GROUP }}
          WEBAPP_NAME: ${{ vars.AZURE_WEBAPP_NAME }}
        run: az webapp deploy --resource-group "$RESOURCE_GROUP" --name "$WEBAPP_NAME" --src-path drop/app.zip --type zip

Azure and GitHub setup

  • Create a federated identity credential in the Azure app registration or managed identity that matches your GitHub repository and the production environment. Give that identity only the permissions it needs to deploy to the app.
  • Set the variables in the workflow to the corresponding Azure IDs and App Service resource names. The IDs are not secrets; OIDC avoids storing a reusable deployment credential.
  • Configure the App Service runtime stack to match your app’s target framework. The ZIP should contain the files inside the publish folder at its root, not the publish folder itself.

For production, protect the production environment with required reviewers, and consider deploying first to an Azure App Service deployment slot. Smoke-test the slot, then swap it into production; that gives you a cleaner release and a straightforward rollback path. If you use a database migration, plan its timing separately—an app deployment workflow should not blindly run a potentially breaking migration.

This keeps CI (restore, test, publish) separate from deployment, while ensuring Azure receives the same tested artifact. For projects with native dependencies, publish on a runner compatible with the App Service operating system and architecture.

Write Your Answer