GitHub Actions for .NET CI/CD: Setup Guide

published on 06 October 2024

GitHub Actions simplifies CI/CD for .NET developers. Here's what you need to know:

  • Built into GitHub, no extra setup needed
  • Responds to GitHub events like pushes and pull requests
  • Free for public repos, 2,000 minutes/month for private ones
  • Access to over 10,000 pre-written actions

Key steps to set up GitHub Actions for .NET CI/CD:

  1. Create a GitHub repo and .NET project
  2. Set up .github/workflows folder
  3. Create YAML workflow file
  4. Configure triggers (e.g., push, pull request)
  5. Set up build job (choose runner, install .NET SDK)
  6. Add tests and code quality checks
  7. Package the app for deployment
  8. Deploy to staging and production environments

Quick Comparison: GitHub Actions vs Jenkins

Feature GitHub Actions Jenkins
Setup Easy, built-in More complex
Customization Good for most cases Highly flexible
Learning curve Gentle (YAML) Steeper
Hosting GitHub or self-hosted Self-hosted
Plugins Growing marketplace Mature ecosystem

This guide covers everything from basic setup to advanced tips for optimizing your .NET CI/CD pipeline using GitHub Actions.

Before you start

Setting up GitHub Actions for .NET CI/CD? Here's what you need:

GitHub and .NET prep

GitHub

1. GitHub account and repo

No GitHub account? Create one. Then:

  • Click "New repository" on GitHub
  • Name it, set to public (free Actions minutes)
  • Clone to your machine

2. .NET project

Your project should:

  • Use .NET Core SDK 6.0+
  • Have working build and tests
  • Include a .NET .gitignore

3. Actions folder

Create .github/workflows in your repo's root. This holds your workflow files.

YAML crash course

GitHub Actions uses YAML. Here's the gist:

name: .NET CI

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-dotnet@v3
        with:
          dotnet-version: '6.0.x'
      - run: dotnet build

This workflow builds your project on every push.

Pro tip: Use spaces, not tabs, in YAML. Indentation matters!

Now you're set to create your first .NET CI/CD workflow with GitHub Actions.

Setting up your GitHub repository

Let's get your GitHub repo ready for CI/CD with GitHub Actions. Here's what you need to do:

Repository settings

Head to your repo's settings page and tweak these key settings:

  1. Turn on branch protection
  2. Require pull request reviews
  3. Enable status checks
  4. Set up auto-merge

Quick setup:

  1. Go to "Settings" in your repo
  2. Click "Branches" on the left
  3. Hit "Add rule" under "Branch protection rules"
  4. Pick your branch (like main)
  5. Choose your rules
  6. Save changes

Managing branches

For smooth CI/CD, set up two main branches:

  1. Main branch: Your production-ready code
  2. Development branch: For ongoing work

To create a new branch:

git checkout -b development
git push -u origin development

That's it! You're now ready to build your CI/CD pipeline with GitHub Actions. Next up: creating your first workflow file.

Creating a workflow file

Let's set up a workflow file for your .NET project. This file tells GitHub Actions how to build, test, and deploy your app.

Workflow file basics

A GitHub Actions workflow file has four main parts:

  1. Name: What your workflow does
  2. Triggers: When it runs
  3. Jobs: Groups of tasks
  4. Steps: Individual tasks

Here's a simple workflow for a .NET project:

name: .NET CI/CD

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: '8.0.x'
    - name: Restore dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --no-restore
    - name: Test
      run: dotnet test --no-build --verbosity normal

This workflow runs when you push or make a pull request to the main branch. It uses Ubuntu, checks out your code, sets up .NET 8, restores dependencies, builds your project, and runs tests.

Where to put the file

GitHub Actions needs your workflow file in a specific spot:

  1. Make a .github folder in your repo's main directory
  2. Inside .github, create a workflows folder
  3. Add your workflow file (like dotnet-ci-cd.yml) in workflows

Your file structure should look like this:

your-repo/
  .github/
    workflows/
      dotnet-ci-cd.yml

You can add this file right on GitHub:

  1. Go to your GitHub repo
  2. Click "Actions"
  3. Pick "New workflow"
  4. Choose "set up a workflow yourself"
  5. Name your file and paste in the YAML
  6. Commit the file

That's it! Your workflow is ready to go.

Setting up workflow triggers

GitHub Actions kicks off your CI/CD when specific events happen in your repo. Here's how to set it up for .NET:

Push and pull request triggers

Want your workflow to run when someone pushes to main or opens a PR? Here's the YAML:

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

This fires when:

  • Code hits the main branch
  • A PR targeting main is opened, reopened, or updated

Need more specific triggers? Try this:

on:
  push:
    branches:
      - 'main'
      - 'releases/**'
    tags:
      - 'v*'

Now your workflow runs when:

  • Main gets new code
  • Any "releases/" branch is updated
  • A new "v" tag is created

Timed triggers

Got tasks that need a schedule? Use schedule with cron:

on:
  schedule:
    - cron: '0 2 * * 0'  # 2 AM every Sunday

Want multiple schedules? No problem:

on:
  schedule:
    - cron: '0 2 * * 0'  # 2 AM every Sunday
    - cron: '0 12 * * 1-5'  # Noon Monday to Friday

Here's a quick rundown of trigger types:

Trigger When It Runs What It's For
Push Code pushed to specific branches Run tests, build code
Pull Request PRs opened, reopened, or updated Check proposed changes
Schedule Set times (cron syntax) Regular maintenance

Setting up the build job

Let's create a build job for our .NET project. This job will run our code, restore dependencies, and compile the app.

Choosing where to run

First, pick an environment for your build:

Runner OS Best for
ubuntu-latest Linux Most .NET Core projects
windows-latest Windows .NET Framework or Windows-specific builds
macos-latest macOS iOS or macOS apps

For most .NET Core projects, go with ubuntu-latest. It's fast and works well with cross-platform .NET apps.

In your workflow:

jobs:
  build:
    runs-on: ubuntu-latest

Installing .NET SDK

Next, set up the .NET SDK:

- name: Setup .NET
  uses: actions/setup-dotnet@v4
  with:
    dotnet-version: '6.0.x'

Need multiple versions? List them:

- name: Setup .NET
  uses: actions/setup-dotnet@v4
  with:
    dotnet-version: |
      3.1.x
      6.0.x

Getting dependencies and building

Now, restore dependencies and build:

- name: Restore dependencies
  run: dotnet restore

- name: Build
  run: dotnet build --configuration Release --no-restore

Here's a complete build job example:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: '6.0.x'
    - name: Restore dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --configuration Release --no-restore
    - name: Test
      run: dotnet test --no-restore --verbosity normal

This job checks out your code, sets up .NET, restores packages, builds the project, and runs tests. Simple, right?

Adding tests

Let's add tests to your GitHub Actions workflow. This helps catch bugs early and keeps your code in good shape.

Running unit tests

After building your project, add this step to run tests:

- name: Run tests
  run: dotnet test --configuration Release --verbosity normal --logger trx --collect:"XPlat Code Coverage"

This command runs tests, logs results, and collects coverage data.

Got multiple test projects? Combine coverage reports:

- name: Combine Coverage Reports
  uses: danielpalme/ReportGenerator-GitHub-Action@5.2.4
  with:
    reports: "**/*.cobertura.xml"
    targetdir: "${{ github.workspace }}"
    reporttypes: "Cobertura"

Creating test reports

Make test results easy to find:

- name: Upload Test Result Files
  uses: actions/upload-artifact@v4
  with:
    name: test-results
    path: ${{ github.workspace }}/**/TestResults/**/*

Want a detailed view? Use this:

- name: Publish Test Results
  uses: EnricoMi/publish-unit-test-result-action@v2.16.1
  with:
    trx_files: "${{ github.workspace }}/**/*.trx"

It shows test results in pull requests and workflow runs.

For a code coverage report:

- name: Publish Code Coverage Report
  uses: irongut/CodeCoverageSummary@v1.3.0
  with:
    filename: "Cobertura.xml"
    format: markdown

This creates a Markdown summary of your coverage. You can add it to PR comments or docs.

sbb-itb-29cd4f6

Checking code quality

Want to catch issues early and keep your codebase clean? Add automated code checks to your GitHub Actions workflow. Here's how to do it for .NET projects:

Using code checkers

SonarLint and StyleCop are go-to tools for .NET code analysis. Here's how to set them up:

1. SonarLint

First, grab the SonarAnalyzer.CSharp NuGet package. It'll hunt down bugs, vulnerabilities, and code smells. Add this to your workflow:

- name: Install SonarAnalyzer
  run: dotnet add package SonarAnalyzer.CSharp

- name: Run SonarAnalyzer
  run: dotnet build-server shutdown && dotnet build /p:TreatWarningsAsErrors=true

2. StyleCop

For StyleCop, you'll need the StyleCop.Analyzers package. Don't forget to create a Directory.Build.props file in your solution root. Here's the workflow addition:

- name: Install StyleCop
  run: dotnet add package StyleCop.Analyzers

- name: Run StyleCop
  run: dotnet build /p:TreatWarningsAsErrors=true

Setting quality rules

Want to define your code quality standards? Create an .editorconfig file in your repo. It's like a rulebook for your code. Here's a quick example:

root = true

[*.cs]
dotnet_analyzer_diagnostic.category-Style.severity = warning
dotnet_code_quality.CA1062.null_check_validation_methods = ThrowArgumentNullException

To make sure these rules are followed, add this to your workflow:

- name: Enforce code style
  run: dotnet format --verify-no-changes --verbosity diagnostic

With these tools in place, you're on your way to cleaner, more consistent code. Happy coding!

Packaging the app

Let's talk about packaging your .NET app for deployment. It's a crucial step in your CI/CD pipeline.

Creating deployment packages

Here's how to package your .NET app using GitHub Actions:

- name: Publish
  run: dotnet publish -c Release -o ./publish

This builds your app and puts the files in a publish directory.

For web apps, you might want a Web Deploy package:

- name: Create Web Deploy Package
  run: msbuild /p:Configuration=Release /p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="./publish"

This creates a zip with your app and deployment scripts.

Managing versions

Versioning helps track changes. Here's how to do it automatically:

1. Install GitVersion:

- name: Install GitVersion
  uses: gittools/actions/gitversion/setup@v0.9.7
  with:
    versionSpec: '5.x'

2. Get the version:

- name: Determine Version
  id: gitversion
  uses: gittools/actions/gitversion/execute@v0.9.7

3. Use it in your build:

- name: Build
  run: dotnet build --configuration Release /p:Version=${{ steps.gitversion.outputs.semVer }}

This updates your app's version based on Git history.

To include version info in your app, add this to your .csproj:

<PropertyGroup>
  <GenerateAssemblyInfo>true</GenerateAssemblyInfo>
  <AssemblyVersion>$(Version)</AssemblyVersion>
  <FileVersion>$(Version)</FileVersion>
  <InformationalVersion>$(Version)</InformationalVersion>
</PropertyGroup>

Deploying to different environments

Let's set up automated deployments for staging and production using GitHub Actions.

Staging deployment

Create staging-deploy.yml in .github/workflows/:

on:
  push:
    branches:
      - develop

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v3
        with:
          dotnet-version: '6.0.x'
      - run: |
          dotnet build --configuration Release
          dotnet publish -c Release -o ./publish
      - uses: azure/webapps-deploy@v2
        with:
          app-name: 'your-staging-app-name'
          publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE_STAGING }}
          package: ./publish

Add AZURE_WEBAPP_PUBLISH_PROFILE_STAGING secret to your GitHub repo with the staging Azure Web App publish profile.

Production deployment

Create production-deploy.yml in .github/workflows/:

on:
  workflow_dispatch:

jobs:
  deploy-production:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v3
        with:
          dotnet-version: '6.0.x'
      - run: |
          dotnet build --configuration Release
          dotnet publish -c Release -o ./publish
      - uses: azure/webapps-deploy@v2
        with:
          app-name: 'your-production-app-name'
          publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE_PRODUCTION }}
          package: ./publish

Add AZURE_WEBAPP_PUBLISH_PROFILE_PRODUCTION secret with the production Azure Web App publish profile.

This setup triggers staging deployments on pushes to develop and allows manual production deployments from the GitHub Actions tab.

Speed Up Your Workflows

Want faster GitHub Actions workflows for .NET CI/CD? Focus on two things: caching and parallel jobs.

Cache Your Stuff

Caching saves time. It stores files you use a lot. For .NET, cache your NuGet packages.

Here's how:

- name: Setup .NET SDK
  uses: actions/setup-dotnet@v4
  with:
    dotnet-version: 8.0.302
    cache: true
    cache-dependency-path: "**/packages.lock.json"

Add this to your project files:

<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>

Then, restore packages like this:

- name: Restore dependencies
  run: dotnet restore --locked-mode

This keeps package versions consistent and builds faster. Our tests showed a 50 MB cache size. After that, builds grabbed packages from the cache. Way quicker!

Run Jobs Together

Parallel jobs cut workflow time. Here's the setup:

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: dotnet format --verify-no-changes

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: dotnet test

  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: dotnet build

For jobs that need others, use needs:

  deploy:
    needs: [lint, test, build]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: dotnet publish

Want to test on multiple platforms? Use a matrix:

  test:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - run: dotnet test

These tweaks will make your workflows zip along. Try them out!

Checking and fixing workflows

GitHub Actions workflows can act up. Here's how to keep them in line:

Looking at workflow runs

Want to see how your workflow's doing? Easy:

  1. Hit up your GitHub repo
  2. Click "Actions"
  3. Pick your workflow
  4. Click a run for the nitty-gritty

You'll see a rundown of jobs and steps. Green checks? You're golden. Red X's? Houston, we have a problem.

For the command line junkies:

gh run list -L 5 -w "CI"

This shows your last 5 "CI" workflow runs.

Need more details? Try:

gh run view 1234567890 -v --log

Just swap out that number with your actual run ID.

Fixing failed workflows

Workflow on the fritz? Here's your game plan:

  1. Dive into the logs: Where's the fire?
  2. Check the setup: Does your runner have what it needs?
  3. Try it at home: Can you make it fail on your machine?
  4. Freshen up: Old packages can be troublemakers.
  5. What's new?: Did a recent commit throw a wrench in the works?

Here's a real-world fix:

A dev's .NET 6 tests were acing it locally but bombing on GitHub Actions. The error?

error NETSDK1045: The current .NET SDK does not support targeting .NET 6.0.

The fix was simple. They added this to their workflow:

- name: Setup .NET
  uses: actions/setup-dotnet@v1
  with:
    dotnet-version: '6.0.x'

Boom. Problem solved. The runner now had the right .NET version to play ball.

Tips for better workflows

Want to supercharge your .NET CI/CD pipeline with GitHub Actions? Here are two key tips:

Breaking workflows into parts

Split your workflows into smaller, reusable chunks. It's like building with LEGO:

# .github/workflows/build.yml
name: Build
on: [push]
jobs:
  build:
    uses: ./.github/workflows/reusable-build.yml

# .github/workflows/reusable-build.yml
name: Reusable Build
on:
  workflow_call:
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Setup .NET
        uses: actions/setup-dotnet@v1
        with:
          dotnet-version: '6.0.x'
      - run: dotnet build

Now you can reuse that build job anywhere. Less copy-paste, more efficiency.

Keeping secrets safe

Don't be the person who accidentally leaks API keys. Use GitHub Secrets:

  1. Go to your repo's Settings > Secrets
  2. Add a new secret (let's call it API_KEY)
  3. Use it in your workflow:
steps:
  - name: Use API
    env:
      API_KEY: ${{ secrets.API_KEY }}
    run: ./my-script.sh

Your secrets stay encrypted and hidden. No more accidental leaks!

"The worst possible way to handle secrets is to hard code them alongside the CI/CD pipeline code. [...] A good practice is to leverage the GitHub encrypted secrets functionality", says a GitHub security expert.

Remember: Good workflows are like good code - modular, reusable, and secure.

Wrap-up

You've got the tools to set up a solid CI/CD pipeline for .NET projects using GitHub Actions. Here's what we covered:

  • GitHub Actions automates your .NET development workflow
  • Setting up a basic pipeline is easy, but you can customize it
  • Break workflows into reusable parts and secure your secrets

A good CI/CD pipeline evolves. Keep improving your workflow. Here's what to do next:

1. Start small, then grow

Build a simple workflow with build and test steps. Add more as you get comfortable.

2. Watch and improve

Use GitHub's analytics to track your workflow. Find and fix slow spots.

3. Stay current

Keep your actions and .NET SDK up to date. You'll get new features and better security.

Remember: Your pipeline should grow with your project. Keep tweaking it to fit your needs.

Related posts

Read more