06 - CI/CD with GitHub Actions¶
Automate the build and deployment of your .NET application so every commit produces a new Container App revision. This tutorial uses GitHub Actions, Azure Container Registry (ACR), and the Azure Container Apps deploy action.
Infrastructure Context
Service: Container Apps (Consumption) | Network: VNet integrated | VNet: ✅
This tutorial assumes a production-ready Container Apps deployment with a custom VNet, ACR with managed identity pull, and private endpoints for backend services.
flowchart TD
INET[Internet] -->|HTTPS| CA["Container App\nConsumption\nLinux .NET 8"]
subgraph VNET["VNet 10.0.0.0/16"]
subgraph ENV_SUB["Environment Subnet 10.0.0.0/23\nDelegation: Microsoft.App/environments"]
CAE[Container Apps Environment]
CA
end
subgraph PE_SUB["Private Endpoint Subnet 10.0.2.0/24"]
PE_ACR[PE: ACR]
PE_KV[PE: Key Vault]
PE_ST[PE: Storage]
end
end
PE_ACR --> ACR[Azure Container Registry]
PE_KV --> KV[Key Vault]
PE_ST --> ST[Storage Account]
subgraph DNS[Private DNS Zones]
DNS_ACR[privatelink.azurecr.io]
DNS_KV[privatelink.vaultcore.azure.net]
DNS_ST[privatelink.blob.core.windows.net]
end
PE_ACR -.-> DNS_ACR
PE_KV -.-> DNS_KV
PE_ST -.-> DNS_ST
CA -.->|System-Assigned MI| ENTRA[Microsoft Entra ID]
CAE --> LOG[Log Analytics]
CA --> AI[Application Insights]
style CA fill:#107c10,color:#fff
style VNET fill:#E8F5E9,stroke:#4CAF50
style DNS fill:#E3F2FD CI/CD Pipeline Flow¶
graph LR
PUSH[Push to main] --> GHA[GitHub Actions]
GHA --> DOTNET[dotnet publish]
DOTNET --> ACR[ACR Build]
ACR --> DEPLOY[Deploy Revision]
DEPLOY --> HEALTH[Health Check] Prerequisites¶
- Completed 05 - Infrastructure as Code with Bicep
- GitHub repository with your .NET source code
- Azure service principal stored as a GitHub secret
Never expose credentials in workflow logs
Keep all credential material in GitHub Secrets and avoid printing secret-derived values in shell steps. Use masked placeholders in documentation and workflow examples.
Step-by-step¶
-
Configure repository variables and secrets
-
Variables:
RESOURCE_GROUP,APP_NAME,ACR_NAME - Secrets:
AZURE_CREDENTIALS,REGISTRY_USERNAME,REGISTRY_PASSWORD
Example AZURE_CREDENTIALS JSON (masked):
{
"clientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"clientSecret": "<client-secret>",
"subscriptionId": "<subscription-id>",
"tenantId": "<tenant-id>"
}
- Create the GitHub Actions workflow file
Save this as .github/workflows/deploy.yml:
name: Deploy .NET App to ACA
on:
push:
branches: [ main ]
workflow_dispatch:
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Azure Login
uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Build and Push to ACR
uses: azure/docker-login@v2
with:
login-server: ${{ vars.ACR_NAME }}.azurecr.io
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build container image
run: |
docker build -t ${{ vars.ACR_NAME }}.azurecr.io/${{ vars.APP_NAME }}:${{ github.sha }} ./apps/dotnet-aspnetcore
docker push ${{ vars.ACR_NAME }}.azurecr.io/${{ vars.APP_NAME }}:${{ github.sha }}
- name: Deploy to Container App
uses: azure/container-apps-deploy-action@v1
with:
imageToDeploy: ${{ vars.ACR_NAME }}.azurecr.io/${{ vars.APP_NAME }}:${{ github.sha }}
resourceGroup: ${{ vars.RESOURCE_GROUP }}
containerAppName: ${{ vars.APP_NAME }}
- Add unit tests to the pipeline (Recommended)
Before building the container, ensure your .NET tests pass:
- name: Run Tests
run: dotnet test ./apps/dotnet-aspnetcore/AzureContainerApps.csproj --configuration Release
-
Validate rollout behavior
- Trigger the workflow by pushing a change to
main. - Confirm a new revision was created in the Azure Portal or via CLI.
- Confirm traffic moved to the healthy new revision.
az containerapp revision list \ --name "$APP_NAME" \ --resource-group "$RESOURCE_GROUP" \ --query "[].{name:name,active:properties.active,trafficWeight:properties.trafficWeight,healthState:properties.healthState}"???+ example "Expected output"
- Trigger the workflow by pushing a change to
Advanced Topics¶
- Environment-based deployments: Use GitHub Environments with protection rules for manual approval before deploying to production.
- Bicep integration: Run
az deployment group createwithin the workflow to ensure infrastructure stays in sync with code. - Canary releases: Use the
azure/container-apps-deploy-actionto deploy with 0% traffic initially, then gradually ramp up after verification.
Use immutable image tags
Always tag your images with ${{ github.sha }} or a semantic version. This ensures that every revision in Container Apps can be traced back to a specific commit in your repository.
See Also¶
- 02 - First Deploy to Azure Container Apps
- 05 - Infrastructure as Code with Bicep
- GitHub Actions for Azure (Microsoft Learn)