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.