Home C# / .NET CI/CD for .NET with GitHub Actions: Automate Build, Test, Deploy
Intermediate 3 min · July 13, 2026

CI/CD for .NET with GitHub Actions: Automate Build, Test, Deploy

Learn to set up CI/CD pipelines for .NET apps using GitHub Actions.

N
Naren Founder & Principal Engineer

20+ years shipping production .NET services in enterprise systems. Lessons pulled from things that broke in production.

Follow
Production
production tested
July 13, 2026
last updated
2,043
articles · all by Naren
Before you start⏱ 15-20 min read
  • Basic knowledge of .NET and C#
  • A GitHub account and a .NET project in a repository
  • Familiarity with YAML syntax
  • An Azure subscription (for deployment section)
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • GitHub Actions automates building, testing, and deploying .NET apps.
  • Define workflows in YAML files in the .github/workflows directory.
  • Use setup-dotnet action to install the correct SDK version.
  • Run tests with dotnet test and publish with dotnet publish.
  • Deploy to Azure Web Apps using azure/webapps-deploy action.
✦ Definition~90s read
What is CI/CD for .NET with GitHub Actions?

GitHub Actions is a CI/CD platform that automates your .NET build, test, and deployment workflows directly from your GitHub repository.

Imagine you're a chef who has to cook a complex meal every time an order comes in.
Plain-English First

Imagine you're a chef who has to cook a complex meal every time an order comes in. Instead of doing everything by hand, you set up an automated kitchen: a robot chops vegetables, another stirs the pot, and a third plates the dish. CI/CD with GitHub Actions is like that automated kitchen for your code. Every time you push changes to GitHub, it automatically builds your code, runs tests, and deploys it to production—so you don't have to do it manually.

In modern software development, delivering features quickly and reliably is crucial. Continuous Integration and Continuous Deployment (CI/CD) pipelines automate the process of building, testing, and deploying applications. For .NET developers, GitHub Actions provides a powerful, integrated CI/CD platform directly within your repository. This tutorial will guide you through creating a production-ready CI/CD pipeline for a .NET application using GitHub Actions. You'll learn how to set up workflows that build your code, run unit and integration tests, perform security scanning, and deploy to Azure App Service. We'll cover real-world scenarios like handling secrets, caching dependencies, and conditional deployments. By the end, you'll have a robust pipeline that ensures every commit is automatically validated and deployed, reducing manual errors and speeding up your release cycle.

What is GitHub Actions and Why Use It for .NET?

GitHub Actions is a CI/CD platform that allows you to automate your build, test, and deployment pipeline directly from your GitHub repository. For .NET developers, it offers a seamless way to integrate with the .NET CLI and Azure services. You define workflows as YAML files in the .github/workflows directory. Each workflow consists of one or more jobs that run on GitHub-hosted runners (Ubuntu, Windows, macOS) or self-hosted runners. Key benefits include tight integration with GitHub, a large marketplace of pre-built actions, and generous free tier for public repositories. For .NET specifically, you can use actions like setup-dotnet to install the SDK, and azure/webapps-deploy to deploy to Azure App Service.

basic-workflow.ymlCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
name: .NET CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Setup .NET
      uses: actions/setup-dotnet@v3
      with:
        dotnet-version: 6.0.x
    - name: Restore dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --no-restore
    - name: Test
      run: dotnet test --no-build --verbosity normal
Output
Build succeeded.
Test run for ... passed.
🔥Workflow Triggers
📊 Production Insight
Always specify the .NET SDK version explicitly to avoid unexpected breaking changes from SDK updates.
🎯 Key Takeaway
GitHub Actions automates your .NET build, test, and deploy pipeline using YAML workflows.

Setting Up Your First .NET CI Pipeline

Let's create a CI pipeline that builds and tests a .NET application on every push. Start by creating a file named .github/workflows/ci.yml in your repository. The workflow will checkout the code, install the .NET SDK, restore dependencies, build, and run tests. Use the actions/checkout action to fetch your repository. The setup-dotnet action installs the specified .NET SDK version. The dotnet restore command restores NuGet packages. Then dotnet build compiles the project, and dotnet test runs unit tests. You can also add caching for NuGet packages to speed up subsequent runs.

.github/workflows/ci.ymlCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
name: CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Setup .NET
      uses: actions/setup-dotnet@v3
      with:
        dotnet-version: '6.0.x'
    - name: Cache NuGet packages
      uses: actions/cache@v3
      with:
        path: ~/.nuget/packages
        key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
        restore-keys: |
          ${{ runner.os }}-nuget-
    - name: Restore dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --no-restore --configuration Release
    - name: Test
      run: dotnet test --no-build --configuration Release --verbosity normal
Output
Build succeeded.
Test run for ... passed.
💡Caching NuGet Packages
📊 Production Insight
Use --configuration Release for production builds to ensure optimizations are applied.
🎯 Key Takeaway
A basic CI pipeline includes checkout, SDK setup, restore, build, and test steps.

Adding Code Quality and Security Scanning

Beyond building and testing, you should integrate code quality checks and security scanning into your pipeline. Tools like SonarCloud, CodeQL, and .NET's built-in analyzers can catch issues early. For example, you can run dotnet format to check code style, or use the github/codeql-action to perform security analysis. Add a step to run dotnet format --verify-no-changes to enforce formatting. For security, use the CodeQL action to analyze your code for vulnerabilities. You can also integrate with Snyk or Dependabot for dependency scanning.

.github/workflows/ci.yml (extended)CSHARP
1
2
3
4
5
6
7
8
9
10
    - name: Check code formatting
      run: dotnet format --verify-no-changes --verbosity diagnostic
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v2
      with:
        languages: csharp
    - name: Autobuild
      uses: github/codeql-action/autobuild@v2
    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v2
Output
No formatting issues found.
CodeQL analysis completed.
⚠ CodeQL Limitations
📊 Production Insight
Run code formatting checks as a separate job to fail fast without waiting for tests.
🎯 Key Takeaway
Integrate code quality and security scanning early to prevent issues from reaching production.

Deploying to Azure App Service

Once your code passes CI, you can automatically deploy to Azure App Service. The azure/webapps-deploy action simplifies this. First, create an Azure Web App and configure deployment credentials. In your GitHub repository, add the publish profile as a secret (e.g., AZURE_WEBAPP_PUBLISH_PROFILE). Then, in your workflow, add a deploy job that depends on the build job. Use the action to publish the build artifacts and deploy them. You can also use slot deployments for zero-downtime updates.

.github/workflows/deploy.ymlCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
name: Deploy to Azure

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Setup .NET
      uses: actions/setup-dotnet@v3
      with:
        dotnet-version: '6.0.x'
    - name: Restore
      run: dotnet restore
    - name: Build
      run: dotnet build --configuration Release --no-restore
    - name: Publish
      run: dotnet publish --configuration Release --no-build --output ./publish
    - name: Upload artifact
      uses: actions/upload-artifact@v3
      with:
        name: webapp
        path: ./publish
  deploy:
    runs-on: ubuntu-latest
    needs: build
    steps:
    - name: Download artifact
      uses: actions/download-artifact@v3
      with:
        name: webapp
    - name: Deploy to Azure Web App
      uses: azure/webapps-deploy@v2
      with:
        app-name: my-dotnet-app
        slot-name: production
        publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
        package: .
Output
Deployment successful.
💡Using Deployment Slots
📊 Production Insight
Always use secrets for credentials; never hardcode them in the workflow file.
🎯 Key Takeaway
Use azure/webapps-deploy action with publish profile secret for secure deployment.

Handling Environment-Specific Configurations

In real-world applications, you need different configurations for development, staging, and production. Use environment variables and secrets to manage these. In GitHub Actions, you can define environment-specific variables and secrets in the repository settings. Then reference them in your workflow using ${{ vars.VARIABLE_NAME }} for variables and ${{ secrets.SECRET_NAME }} for secrets. For .NET, you can use the appsettings.{Environment}.json pattern and set the ASPNETCORE_ENVIRONMENT variable in the workflow. For example, set ASPNETCORE_ENVIRONMENT: Production in the deploy job.

.github/workflows/deploy.yml (with env vars)CSHARP
1
2
3
4
5
6
7
8
9
10
    - name: Deploy to Azure Web App
      uses: azure/webapps-deploy@v2
      with:
        app-name: my-dotnet-app
        slot-name: production
        publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
        package: .
      env:
        ASPNETCORE_ENVIRONMENT: Production
        ConnectionStrings__Default: ${{ secrets.CONNECTION_STRING }}
Output
Deployment successful.
🔥Environment Variables in .NET
📊 Production Insight
Never store production secrets in your repository; always use GitHub encrypted secrets.
🎯 Key Takeaway
Use GitHub environments and secrets to manage configuration across different stages.

Advanced: Matrix Builds and Conditional Steps

To test your application across multiple .NET versions or operating systems, use matrix builds. Define a matrix strategy in your job to run the same steps with different parameters. You can also add conditional steps using the 'if' keyword. For example, only run deployment on the main branch, or skip certain steps for pull requests. Matrix builds are useful for ensuring compatibility.

.github/workflows/matrix.ymlCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
        dotnet-version: ['6.0.x', '7.0.x']
    steps:
    - uses: actions/checkout@v3
    - name: Setup .NET ${{ matrix.dotnet-version }}
      uses: actions/setup-dotnet@v3
      with:
        dotnet-version: ${{ matrix.dotnet-version }}
    - name: Restore
      run: dotnet restore
    - name: Build
      run: dotnet build --configuration Release
    - name: Test
      run: dotnet test --configuration Release --verbosity normal
    - name: Upload test results
      if: always()
      uses: actions/upload-artifact@v3
      with:
        name: test-results-${{ matrix.os }}-${{ matrix.dotnet-version }}
        path: '**/TestResults/*.trx'
Output
Test run for ubuntu-latest/6.0.x passed.
Test run for windows-latest/7.0.x passed.
💡Always Upload Test Results
📊 Production Insight
Limit matrix builds to essential combinations to avoid excessive CI costs.
🎯 Key Takeaway
Matrix builds allow testing across multiple OS and SDK versions in parallel.

Best Practices and Security Considerations

When setting up CI/CD pipelines, follow security best practices: use least privilege for service principals, rotate secrets regularly, and audit workflow runs. For GitHub Actions, avoid using self-hosted runners for public repositories unless necessary. Use OpenID Connect (OIDC) to authenticate to cloud providers instead of long-lived secrets. For .NET, enable NuGet package signing verification and use lock files. Also, consider adding a manual approval step for production deployments using GitHub Environments with required reviewers.

.github/workflows/deploy-with-approval.ymlCSHARP
1
2
3
4
5
6
7
8
9
10
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
    - name: Deploy
      uses: azure/webapps-deploy@v2
      with:
        app-name: my-dotnet-app
        publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
Output
Deployment approved and successful.
⚠ Environment Protection Rules
📊 Production Insight
Use GitHub Environments to separate staging and production with different protection rules.
🎯 Key Takeaway
Implement security best practices like OIDC, secret rotation, and manual approvals for production.
● Production incidentPOST-MORTEMseverity: high

The Silent Build Failure: When a NuGet Package Update Broke CI

Symptom
The application deployed successfully but crashed on startup with a missing method exception.
Assumption
The developer assumed the CI pipeline would catch any breaking changes because all tests passed locally.
Root cause
A transitive dependency was updated by a NuGet restore, introducing a breaking change that wasn't covered by existing tests. The CI pipeline didn't run integration tests or use package lock files.
Fix
Enabled NuGet package lock files (using PackageReference with RestorePackagesWithLockFile) and added integration tests that exercised the startup path. Also added a step to run dotnet restore with a lock file validation.
Key lesson
  • Always use package lock files to ensure reproducible builds.
  • Include integration tests that cover the application startup and key dependencies.
  • Pin dependency versions in production pipelines to avoid unexpected updates.
  • Run CI on a clean environment (e.g., fresh VM) to catch environment-specific issues.
  • Monitor build logs for warnings that could indicate breaking changes.
Production debug guideSymptom to Action4 entries
Symptom · 01
Build fails with 'NU1603' warning (dependency version mismatch)
Fix
Check NuGet package references and enable lock files. Run dotnet restore --locked-mode locally to reproduce.
Symptom · 02
Tests pass locally but fail on CI
Fix
Ensure CI uses the same .NET SDK version and test runner. Check for environment-specific settings like connection strings.
Symptom · 03
Deployment succeeds but app crashes on startup
Fix
Add health check endpoint and run a post-deployment smoke test. Check startup logs in Azure App Service.
Symptom · 04
Secrets not available in workflow
Fix
Verify secrets are defined in GitHub repository settings and referenced correctly as ${{ secrets.SECRET_NAME }}.
★ Quick Debug Cheat Sheet for GitHub Actions .NET CI/CDCommon issues and immediate actions
Build fails: 'dotnet: command not found'
Immediate action
Add setup-dotnet action before build step
Commands
uses: actions/setup-dotnet@v3
with: dotnet-version: '6.0.x'
Fix now
Add the setup-dotnet step to your workflow
NuGet restore fails with authentication error+
Immediate action
Check if private package source requires credentials
Commands
dotnet nuget add source --username USER --password ${{ secrets.MYGET_PAT }} --store-password-in-clear-text
Fix now
Add NuGet source with credentials in workflow or use GitHub Packages
Tests fail with 'System.InvalidOperationException'+
Immediate action
Look for missing environment variables or configuration
Commands
env: ConnectionStrings__Default: ${{ secrets.CONNECTION_STRING }}
Fix now
Set required environment variables in workflow step
Deployment fails with 'Unauthorized'+
Immediate action
Verify Azure publish profile or service principal
Commands
uses: azure/webapps-deploy@v2
with: publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
Fix now
Add correct publish profile secret to GitHub
FeatureGitHub ActionsAzure Pipelines
IntegrationNative to GitHubPart of Azure DevOps
YAML syntaxYesYes (but different structure)
Hosted runnersLinux, Windows, macOSLinux, Windows, macOS
Free tier2000 min/month for public repos1800 min/month for public projects
MarketplaceLarge ecosystem of actionsExtensions and tasks
Secrets managementEncrypted secretsVariable groups and library
Deployment to AzureVia actionsNative tasks
⚙ Quick Reference
7 commands from this guide
FileCommand / CodePurpose
basic-workflow.ymlname: .NET CIWhat is GitHub Actions and Why Use It for .NET?
.githubworkflowsci.ymlname: CISetting Up Your First .NET CI Pipeline
.githubworkflowsci.yml (extended)- name: Check code formattingAdding Code Quality and Security Scanning
.githubworkflowsdeploy.ymlname: Deploy to AzureDeploying to Azure App Service
.githubworkflowsdeploy.yml (with env vars)- name: Deploy to Azure Web AppHandling Environment-Specific Configurations
.githubworkflowsmatrix.ymljobs:Advanced
.githubworkflowsdeploy-with-approval.ymljobs:Best Practices and Security Considerations

Key takeaways

1
GitHub Actions provides a powerful CI/CD platform for .NET applications with minimal setup.
2
Always use explicit SDK versions, caching, and secrets to ensure reliable and secure pipelines.
3
Integrate code quality and security scanning early in the pipeline to catch issues before deployment.
4
Use matrix builds and environment-specific configurations to test across multiple platforms and stages.
5
Follow security best practices like OIDC and manual approvals for production deployments.

Common mistakes to avoid

4 patterns
×

Not specifying the .NET SDK version in setup-dotnet, causing builds to use a different version than local.

×

Hardcoding secrets in the workflow file.

×

Running tests without building first.

×

Not using caching for NuGet packages, causing long restore times.

INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
Explain the structure of a GitHub Actions workflow file.
Q02SENIOR
How would you set up a CI pipeline for a .NET solution that has multiple...
Q03SENIOR
How do you securely deploy to Azure from GitHub Actions?
Q01 of 03JUNIOR

Explain the structure of a GitHub Actions workflow file.

ANSWER
A workflow file is a YAML file in .github/workflows. It contains a name, on (trigger events), jobs, and steps. Each job runs on a runner and consists of steps that can run commands or use actions.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
How do I run a GitHub Actions workflow manually?
02
Can I use GitHub Actions with on-premises servers?
03
How do I debug a failing workflow step?
04
What is the difference between GitHub Actions and Azure Pipelines?
05
How do I cache NuGet packages in GitHub Actions?
N
Naren Founder & Principal Engineer

20+ years shipping production .NET services in enterprise systems. Lessons pulled from things that broke in production.

Follow
Verified
production tested
July 13, 2026
last updated
2,043
articles · all by Naren
🔥

That's ASP.NET. Mark it forged?

3 min read · try the examples if you haven't

Previous
OpenTelemetry for .NET Applications
29 / 35 · ASP.NET
Next
Containerizing .NET Applications with Docker