.NET GitHub Actions Workflow Template
Last updated
This is a working .github/workflows/ci.yml for a .NET solution. It installs the SDK, caches the NuGet package folder, restores once, builds in Release mode and runs the tests without rebuilding.
The workflow
name: CI
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
DOTNET_NOLOGO: "true"
DOTNET_CLI_TELEMETRY_OPTOUT: "true"
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: "true"
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
- uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props', '**/packages.lock.json') }}
restore-keys: |
nuget-${{ runner.os }}-
- run: dotnet restore
- run: dotnet build --no-restore --configuration Release
- run: dotnet test --no-build --configuration Release --verbosity normal
What the non-obvious parts do
dotnet-version: 9.0.xtakes the latest 9.0 patch. Use the major your project targets, orglobal-json-file: global.jsonto follow the SDK pinned in your repo.- The NuGet cache step stores
~/.nuget/packages.setup-dotnethas acache: trueoption, but it requires apackages.lock.jsonlock file, so an explicitactions/cachestep is more broadly usable. Key it on project files so adding a package makes a new cache. restore, then--no-restore, then--no-build. Each step does one job. Without the flags,buildandtestwould each repeat the earlier steps, wasting time.- Environment variables. The three
DOTNET_settings remove the welcome banner, telemetry and first-run work that clutters logs. - Release configuration. Tests run against what you ship. Use Debug only if tests rely on debug symbols.
- Linux runner. .NET runs cross-platform, and Linux minutes are the cheapest.
Variants
SQL Server service container
services:
mssql:
image: mcr.microsoft.com/mssql/server:2022-latest
env:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: "Ci_Passw0rd!"
ports:
- 1433:1433
options: >-
--health-cmd "/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P Ci_Passw0rd! -Q 'SELECT 1'"
--health-interval 10s
--health-retries 10
Then set the connection string through an environment variable. The SQL Server image needs more memory and time to start than Postgres. A Postgres service, as in the Django template, uses the same structure.
Multiple target frameworks
Add a matrix with dotnet-version: [8.0.x, 9.0.x]. See matrix builds.
Code coverage
Add --collect:"XPlat Code Coverage" to dotnet test and upload the results as an artifact.
Publish and deploy
Add a second job with needs: build on main that runs dotnet publish -c Release -o out, and deploy using OIDC.
Windows or macOS
Add them to a matrix for desktop or cross-platform libraries. Windows costs more per minute, so use it where needed.
Run it on faster machines
Change one line:
runs-on: [self-hosted, Linux, compiler-ci]
The job runs on a fresh 8 vCPU machine from compiler.dev, with automatic fallback to your original runner. compiler.dev's benchmark of React with ASP.NET Core and SQL Server went from 3m34s to 1m41s; see benchmarks. More in speed up .NET on GitHub Actions, and prices on pricing.
FAQ
Why not use setup-dotnet's cache option?
It only works when you use NuGet lock files. If you do, cache: true is simpler.
Do I need to restore separately?
Not strictly, but a separate restore makes the cache effect visible in the logs and lets you use --no-restore afterward.
How do I run tests in parallel?
xUnit and NUnit parallelise within a project. For more, use test sharding by project.
What about the SQL Server password rules?
The sa password must meet complexity rules or the container exits. The example password does.
Related
- Speed Up .NET in GitHub ActionsSpeed up .NET builds in GitHub Actions: NuGet caching with setup-dotnet, --no-restore, --no-build, parallel dotnet test, and SQL Server services.
- Django GitHub Actions Workflow TemplateCopy-paste GitHub Actions CI for Django with a Postgres service container, pip caching, migrations check and tests, plus concurrency and permissions.
- Docker Build GitHub Actions Workflow TemplateCopy-paste GitHub Actions workflow to build Docker images with Buildx, GitHub cache layers and a push to GHCR on main, with minimal permissions.
- Elixir GitHub Actions Workflow TemplateCopy-paste GitHub Actions CI for Elixir and Phoenix with a Postgres service, deps and _build caching via erlef/setup-beam, plus format and tests.