
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Shipping .NET applications reliably requires automating the path from commit to production without manual intervention or fragile scripts. A properly configured CI/CD pipeline for .NET with GitHub Actions handles restoration, testing, building, and deployment in a single YAML workflow that lives alongside your code. This guide walks through constructing a production-grade workflow that integrates security scanning, containerization, and passwordless Azure deployment.
How do you structure a CI/CD pipeline for .NET with GitHub Actions?
The foundation of any reliable automation is a predictable directory structure and a workflow that separates concerns. In practice, I keep workflows in .github/workflows/ with descriptive names like dotnet-ci.yml and dotnet-deploy.yml. Separating continuous integration from deployment allows you to run fast feedback loops on every PR while restricting production deployments to protected branches.
Your workflow should always pin specific SDK versions rather than relying on latest. Reproducibility matters more than convenience when debugging failed builds at 2 AM. For teams managing multiple services, consider reading about GitHub Actions reusable workflows and matrix builds to avoid duplicating logic across repositories.
Defining triggers and environment variables
Start by declaring precise triggers. Running on every push to main and every pull request targeting main covers most scenarios. Use paths-ignore to skip runs for documentation-only changes. Define environment variables at the workflow level for values like the .NET version and project path, making future upgrades a single-line change.
name: .NET CI/CD Pipeline
on:
push:
branches: [ main ]
paths-ignore: [ '.md', 'docs/' ]
pull_request:
branches: [ main ]
env:
DOTNET_VERSION: '9.0.x'
PROJECT_PATH: './src/MyApp.Web'
IMAGE_NAME: 'ghcr.io/${{ github.repository }}' How do you restore, test, and build .NET projects in GitHub Actions?
The core CI job must be fast and deterministic. NuGet package restoration is often the bottleneck; caching reduces restore times from minutes to seconds. The actions/setup-dotnet action handles SDK installation and includes built-in caching when you specify cache: 'nuget'. Always run dotnet restore explicitly before building so cache hits are visible in logs.
Testing deserves special attention. Run dotnet test with the --no-restore flag since you already restored in the previous step. Add --logger trx to generate test result files and use dorny/test-reporter to publish results directly in the PR check. This visibility prevents merging broken code. If your application depends on databases, explore integration testing in CI pipelines using service containers.
Publishing and uploading artifacts
After tests pass, publish the application as a self-contained unit. Use dotnet publish -c Release --no-restore -o ./publish to create a deployment-ready folder. Upload this directory using actions/upload-artifact@v4. Artifacts bridge the gap between your CI and CD jobs without rebuilding. Set a retention period of 1–3 days to manage storage costs; old artifacts rarely help debugging.
How do you containerize .NET apps securely in GitHub Actions?
Containers provide consistency between local development and cloud infrastructure. Multi-stage Dockerfiles are non-negotiable for .NET; they reduce final image size by excluding the SDK. Your build stage uses mcr.microsoft.com/dotnet/sdk:9.0, while the runtime stage uses mcr.microsoft.com/dotnet/aspnet:9.0-alpine. Alpine-based images minimize attack surface and improve pull times.
Never store Docker credentials as repository secrets if deploying to Azure or AWS. Instead, leverage OpenID Connect (OIDC). Configure a federated credential in your cloud provider and use azure/login@v2 with client-id, tenant-id, and subscription-id. This eliminates long-lived secrets entirely. For teams handling sensitive data, review handling secrets in CI/CD pipelines safely before proceeding.
- name: Log in to Azure via OIDC
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Login to ACR
run: az acr login --name myregistry
- name: Build and push Docker image
run: |
docker build -t myregistry.azurecr.io/myapp:${{ github.sha }} .
docker push myregistry.azurecr.io/myapp:${{ github.sha }} Scanning images before deployment
Integrate aquasecurity/trivy-action immediately after building. Configure it to fail on HIGH and CRITICAL vulnerabilities. Shift-left security catches issues before they reach production. Generate an SBOM (Software Bill of Materials) alongside your scan for compliance audits. This evidence collection aligns with SOC 2 requirements and demonstrates due diligence during reviews.
How do you deploy .NET to Azure App Service or AKS from GitHub Actions?
Deployment strategy depends on your hosting target. Azure App Service suits most web APIs and MVC applications with minimal operational overhead. Azure Kubernetes Service (AKS) fits microservices architectures requiring fine-grained scaling. Both support OIDC authentication through the same azure/login action used earlier.
| Criteria | Azure App Service | Azure Kubernetes Service |
|---|---|---|
| Setup Complexity | Low — managed PaaS | High — cluster management required |
| Scaling Model | Instance count / auto-scale rules | HPA + Cluster Autoscaler |
| Deployment Action | azure/webapps-deploy | azure/k8s-deploy or Helm |
| Best For | Monoliths, APIs, small teams | Microservices, high-scale platforms |
| Cost Baseline | Predictable per-instance pricing | Higher base cost + node pools |
Deploying to Azure App Service with slot swaps
Use deployment slots to achieve zero-downtime releases. Deploy to a staging slot first, run smoke tests against it, then swap into production. The azure/webapps-deploy@v3 action supports this natively via the slot-name parameter. Always configure health checks in App Service so the platform only routes traffic to healthy instances after swap.
Deploying to AKS with manifests or Helm
For Kubernetes, choose between raw manifests and Helm charts based on complexity. Simple deployments work fine with azure/k8s-deploy and baked manifests. Complex environments benefit from Helm templating. Reference deploying to AKS with Azure Pipelines for patterns that translate directly to GitHub Actions. Pin image tags to commit SHAs, never latest, to ensure traceability.
What are common mistakes when building .NET CI/CD pipelines?
Several anti-patterns consistently cause failures in production. Avoiding them saves hours of debugging and prevents security incidents.
- Hardcoding secrets: Never embed connection strings or API keys in YAML. Use GitHub Environments with required reviewers for production deployments.
- Skipping cache configuration: Uncached NuGet restores add 2–5 minutes per job. Enable caching in
setup-dotnetor useactions/cachemanually. - Running as root in containers: Add
USER appin your Dockerfile. Root containers violate least-privilege principles and fail security scans. - Ignoring test failures: Configure workflows to fail fast. A green checkmark on a broken build erodes trust in the entire system.
- Using mutable tags: Tag images with
${{ github.sha }}or semantic versions. Mutable tags likedevmake rollbacks impossible.
Observability completes the pipeline. Without monitoring, you cannot verify deployments succeeded beyond HTTP 200 responses. Integrate OpenTelemetry early and correlate deployment events with metrics. Teams new to observability should start with OpenTelemetry as the observability standard to instrument .NET applications consistently.
Implementing Your CI/CD Pipeline for .NET with GitHub Actions
A well-architected CI/CD pipeline for .NET with GitHub Actions transforms deployment from a risky manual process into a repeatable, auditable workflow. Start with the CI foundation—restore, test, build, scan—before adding deployment stages. Adopt OIDC authentication from day one to eliminate secret sprawl. Containerize with multi-stage builds and enforce security gates before production. Whether targeting App Service or AKS, prioritize zero-downtime strategies and immutable artifacts.
If your team needs help designing or auditing .NET automation workflows, reach out to discuss your CI/CD requirements. I help organizations build pipelines that are secure, observable, and audit-ready from the first commit.