.NET Builds on GitHub Actions

.NET builds on GitHub Actions run on WarpBuild Windows runners from 4 to 32 vCPU at $0.016 to $0.128 per minute, with NuGet caching through actions/cache.

Last verified:

A .NET build on a Windows runner in GitHub Actions needs three things: enough vCPUs for MSBuild to compile projects in parallel, a NuGet package cache that survives between jobs, and an image carrying the Visual Studio toolset your solution expects. WarpBuild provides Windows runners from 4 to 32 vCPUs at $0.016 to $0.128 per minute, selected by pointing runs-on at a label such as warp-windows-latest-x64-8x.

Switching is a one-line change to the workflow file. The sections below cover the exact configuration including NuGet caching, which runner size fits restore-heavy and test-heavy solutions, the bottlenecks specific to MSBuild and the .NET SDK, and how to pick between the Windows Server 2022, Windows Server 2025, and Visual Studio 2026 images.

Overview

A .NET pipeline spends its Windows minutes in four places: NuGet restore, MSBuild walking the project graph and invoking the compiler, test execution, and packaging or publish steps. Restore is network and disk bound. Compilation parallelizes across independent projects. Tests parallelize as far as the test framework allows. Publish is mostly single-threaded I/O.

Windows runners earn their place when the target requires Windows: .NET Framework projects on net48 and earlier, WPF and WinForms applications, C++/CLI assemblies, VSIX extensions, MSIX packaging, and anything that P/Invokes Win32 during tests. Cross-platform class libraries and ASP.NET Core services can build on cheaper Linux runners, so a common split is Linux jobs for the portable code and Windows jobs for the desktop and Framework targets. The larger runners cost guide covers that split in detail.

WarpBuild provides Linux x64, Linux ARM64, macOS, and Windows runners. The Windows fleet comes in three image families, each in four sizes from 4 to 32 vCPUs with 256GB SSDs: Windows Server 2022 under the warp-windows-latest-x64-<size> labels, Windows Server 2025 under warp-windows-2025-x64-<size>, and Windows Server 2025 with Visual Studio 2026 under warp-windows-2025-vs2026-x64-<size>. The full matrix is in the cloud runners documentation and on the Windows runner hub.

Every runner is an ephemeral VM, freshly allocated for the job and destroyed afterward. The images carry the same tooling as GitHub-hosted Windows runners, documented in the preinstalled software list, so MSBuild, the .NET SDKs, and Visual Studio Build Tools are present without setup steps. Run as many jobs as your workflows need. Generally available Linux and Windows runners do not have plan-level concurrency caps.

Configuration

Here is a working .NET pipeline on warp-windows-latest-x64-8x. It restores with a committed lock file, caches the NuGet packages folder with actions/cache, and reuses the restore and build outputs in later steps.

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

env:
  NUGET_PACKAGES: ${{ github.workspace }}\.nuget\packages
  DOTNET_NOLOGO: true

jobs:
  build-and-test:
    runs-on: warp-windows-latest-x64-8x
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-dotnet@v4
        with:
          global-json-file: global.json

      - uses: actions/cache@v4
        with:
          path: ${{ github.workspace }}\.nuget\packages
          key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
          restore-keys: |
            ${{ runner.os }}-nuget-

      - run: dotnet restore --locked-mode

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

      - run: dotnet test --no-build --configuration Release

Four details matter here.

Cache NuGet with actions/cache, and only with actions/cache. WarpBuild's built-in cache is available on Linux runners only, so Windows jobs use GitHub's cache service through actions/cache or a local packages folder committed to the workspace drive. The cache entry lands in GitHub's backend and counts against GitHub's 10 GB per-repository cache limit, so keep the cached path narrow: the packages folder, without bin and obj directories.

Pin NUGET_PACKAGES inside the workspace. By default NuGet extracts packages to the user profile. Setting NUGET_PACKAGES to a workspace path gives the cache step one deterministic folder to save and restore, keeps extraction on the same SSD as the checkout, and avoids profile-relative paths that differ between images.

Commit lock files and restore with --locked-mode. Set RestorePackagesWithLockFile to true in Directory.Build.props, run a local restore to generate packages.lock.json per project, and commit the files. Restore then skips dependency resolution and verifies the graph against the lock file, and hashFiles('**/packages.lock.json') gives the cache a key that changes exactly when the dependency set changes. Without lock files, key on hashFiles('**/*.csproj') instead and accept coarser invalidation.

Build once, then reuse. --no-restore on the build step and --no-build on the test step stop the SDK from repeating work inside the job. Each repeated implicit restore or rebuild pays MSBuild startup and evaluation again on the same machine.

The default shell on Windows runners is PowerShell, so multi-line run steps use PowerShell syntax and backslash paths work as written above.

Choosing a Windows image

Three image families cover different toolset needs, all at the same per-minute rates.

warp-windows-latest-x64-<size> resolves to Windows Server 2022 today, with warp-windows-2022-x64-<size> as the pinned alias. It ships Visual Studio 2022 and matches the tooling of GitHub's windows-2022 image. This is the default choice for solutions that build with the VS 2022 toolset, which as of 2026 is most production .NET work.

warp-windows-2025-x64-<size> runs the Windows Server 2025 base and still ships Visual Studio 2022. Pick it to build on the newer OS base, for example when tests exercise OS behavior that differs between Server 2022 and Server 2025, while keeping the same compiler toolset.

warp-windows-2025-vs2026-x64-<size> is a transitional label set: the same Windows Server 2025 base with Visual Studio 2026 installed instead of Visual Studio 2022. Pick it when the solution needs the VS 2026 toolset, such as a newer MSVC toolchain for C++/CLI projects or MSBuild behavior introduced with VS 2026. The labels mirror GitHub's own rollout of the Visual Studio 2026 image, and Visual Studio 2026 may become the default on the Windows Server 2025 labels in a later update. If the toolset version matters to your build, pin the explicit label rather than latest.

Projects mixing C++ and .NET in one solution should confirm the platform toolset version against the image before switching image families; the sibling page on C++ builds covers the native side.

Sizing

The Windows catalog, with per-minute rates from the WarpBuild pricing page:

Runner labelOSvCPUMemoryStoragePrice per minute
warp-windows-latest-x64-4xWindows Server 2022416 GB256GB SSD$0.016
warp-windows-latest-x64-8xWindows Server 2022832 GB256GB SSD$0.032
warp-windows-latest-x64-16xWindows Server 20221664 GB256GB SSD$0.064
warp-windows-latest-x64-32xWindows Server 202232128 GB256GB SSD$0.128

The warp-windows-2025-x64-<size> and warp-windows-2025-vs2026-x64-<size> labels come in the same four sizes at the same rates. Windows 2 vCPU runners were removed on June 8, 2026, so 4 vCPU is the smallest Windows size.

Restore-heavy and test-heavy solutions size differently.

Restore-heavy solutions have large package graphs and modest amounts of code: a service with hundreds of transitive NuGet dependencies and twenty projects. Restore time is bound by network transfer and disk extraction, and extra vCPUs shorten neither. Keep these on warp-windows-latest-x64-4x or warp-windows-latest-x64-8x and spend the effort on lock files and a warm cache, which remove most of the restore minutes outright.

Wide build graphs reward cores. MSBuild schedules independent projects across available vCPUs, so a solution with 100 projects and a shallow dependency tree keeps 16 vCPUs busy through the compile phase. warp-windows-latest-x64-16x fits here. Long dependency chains cap this: projects on the critical path build one after another regardless of core count, so a deep narrow graph gains little above 8 vCPUs.

Test-heavy solutions depend on how the tests parallelize. xUnit runs test collections in parallel within an assembly, and VSTest can run assemblies concurrently when configured. Parallel-safe suites saturate warp-windows-latest-x64-16x or warp-windows-latest-x64-32x and finish in proportionally fewer minutes. Serialized integration tests leave cores idle, so keep them on the 8x size and shard across jobs instead, keeping in mind that each extra job pays its own restore and warmup.

Memory rarely binds before CPU does, but MSBuild worker nodes, the Roslyn compiler server, and test hosts add up on large solutions; the 32 GB on the 8x size is a comfortable floor for solutions above 50 projects.

Worked cost model

GitHub publishes per-minute list prices for its hosted runners. Windows larger runners meter at $0.022 per minute for 4 vCPU, $0.042 for 8 vCPU, $0.082 for 16 vCPU, and $0.162 for 32 vCPU. Rates are from the GitHub Actions minute multipliers reference, checked on 2026-08-13.

Take a .NET solution whose pull request pipeline consumes 20,000 Windows runner-minutes per month on 8 vCPU machines:

Line itemRateVolumeMonthly cost
GitHub-hosted Windows 8 vCPU larger runner$0.042 per minute20,000 minutes$840.00
warp-windows-latest-x64-8x$0.032 per minute20,000 minutes$640.00

The WarpBuild total is $640.00 against $840.00 for the same minutes, a difference of $200.00 per month. As a standalone rate comparison: warp-windows-latest-x64-8x (8 vCPU, 32 GB) costs $0.032 per minute against $0.042 per minute for the 8-core Windows larger runner (8 vCPU, 32 GB): 24 percent lower list price. GitHub list price checked on 2026-08-13. The model holds minutes equal on both sides. NuGet caching through actions/cache uses GitHub's cache service, which carries no per-operation charge, so the runner minutes are the whole WarpBuild bill here.

Full rates for every size and platform are on the pricing page.

Bottlenecks

Four bottlenecks account for most slow .NET pipelines on GitHub Actions.

NuGet restore over the network. Every runner starts as a fresh VM, so an uncached job resolves the full dependency graph, downloads every package from nuget.org and any private feeds, and extracts them to disk before the compiler runs. On solutions with heavy package graphs this dwarfs compilation. The fix is layered: lock files with --locked-mode remove resolution work, actions/cache on the packages folder removes the downloads, and NUGET_PACKAGES inside the workspace keeps extraction on the local SSD. When the cache misses after a lock file change, restore-keys falls back to the newest previous entry so restore only fetches the packages that changed.

MSBuild node reuse across jobs. MSBuild keeps worker node processes and the Roslyn compiler server warm between invocations on the same machine. On ephemeral runners those processes cannot outlive the job, so a workflow that splits restore, build, and test into three separate GitHub Actions jobs pays VM boot, SDK warmup, cache restore, and MSBuild startup three times over. Consolidate the sequence into one job, as in the workflow above, and let --no-restore and --no-build carry state between steps. Reserve separate jobs for work that genuinely runs in parallel, such as sharded test suites.

First-run JIT. The first dotnet invocation on a fresh VM pays one-time costs: assembly loading and JIT compilation of MSBuild tasks, NuGet components, analyzers, and source generators that lack precompiled native images. This shows up as a slow first step that no amount of caching removes. Minimizing the number of separate dotnet invocations bounds the cost to one warmup per job, and pruning unused analyzers from the build trims what the JIT has to process on every run.

Large solution graphs. A 200-project .sln makes every pipeline step pay graph evaluation, even for changes touching one leaf project. Solution filters (.slnf files) scope restore, build, and test to a subsystem, so pull request jobs build the filter covering the changed area while the full solution builds on main. Deep dependency chains are the other cost: the critical path serializes, so flattening project references where possible raises the parallelism that a 16x or 32x runner can actually use.

Telling these apart is a measurement problem. WarpBuild's CI observability streams system metrics from the runner and correlates them with GitHub Actions job logs, so a job stuck on single-threaded restore looks visibly different from a build saturating all cores through a wide graph. Broader tactics for the Windows fleet, including image choice and job structure, are in the guide to faster Windows builds on GitHub Actions.

Proof

The runner-side agent is open source at github.com/WarpBuilds/warpbuild-agent, and the cloud runners documentation publishes the full Windows catalog with prices, image contents, and change history for independent verification.

FAQ

Which WarpBuild Windows runner size should a .NET solution start with?

Start on warp-windows-latest-x64-8x at $0.032 per minute. Watch the CPU chart in WarpBuild's CI observability during the build step. Move to warp-windows-latest-x64-16x when all 8 vCPUs stay busy through compilation, and keep formatting and analyzer-only jobs on warp-windows-latest-x64-4x.

Can I use the WarpBuild cache on Windows runners?

No. WarpBuild's built-in cache is available on Linux runners only. On Windows runners, cache the NuGet packages folder with actions/cache, or point NUGET_PACKAGES at a folder inside the workspace and cache that path.

Do WarpBuild Windows runners include Visual Studio and the .NET SDKs?

Yes. The images carry the same tooling as the GitHub-hosted Windows images. The Windows Server 2022 and Windows Server 2025 labels ship Visual Studio 2022, and the warp-windows-2025-vs2026-x64 labels ship Visual Studio 2026.

Is WarpBuild SOC 2 compliant?

The audit evidence is published at trust.warpbuild.com.

Start with $10 in free credits

Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.