Have you ever noticed that when using GitHub hosted runners, Windows runners are just... slow? In this article, I demonstrate why and what we can do about it, using .NET workloads as an example.
GitHub Actions Hosted Windows Runners, Slower-than-expected CI, and You
Have you ever noticed that when using GitHub hosted runners, Windows runners are just... slow? No, this is not a dig at all to GitHub's hosted runner offerings. It's quite convenient after all to have fully managed, ephemeral runners with high availability and the breadth of tools that are kept up to date on a semi-daily basis. I'm glad I don't have to roll my own solution or maintain such a thing, personally! With that said, if you find yourself having to use Windows runners, whether that's because you develop Windows-specific components or need to test your software on numerous platforms (thank you for that!), you'll notice that your CI times compared to Linux runners can be just plain slow, if not frustrating.
TL;DR
Windows runners in GitHub Actions can be slow due to I/O bottlenecks, especially on the C: drive. You can speed things up by:
- Moving heavy I/O operations to the D: drive (temp storage) using environment variables
- Tweaking .NET 8+/.NET Framework builds to trade disk I/O for CPU cycles
- Using Dev Drive or RAM disk for even faster storage (if you're feeling adventurous!)
Table of Contents
- Windows Hosted Runners are slow... sometimes?
- So, what can we do about this? (Easy Wins)
- So, what else can we do? (Harder wins)
- Anything else?
Windows Hosted Runners are slow... sometimes?
I noticed around 2022 or 2023 some significant and sudden slowness in my Windows builds on GitHub Actions. Of course, I'm not the only one that's noticed. In my quest to find the reason for the slowness and to relieve myself of possibly context switching down into the depths of the next rabbit hole due to excessively slow CI, I found many other instances of slow performance on the runners.
- Windows runners are consistently extremely slow compared to Linux and macOS #7320
- [Windows] C drive is significantly slower than D drive which causes issue with workflow runtime being significantly increased in general #8755
- extremely slow Network and Disk IO on Windows agent compared to Ubuntu/Mac #3577
There's many other examples of slowness in GitHub issues that are apparently related to slow IO operations on the Windows runners if you want to search around.
As many others in these issues point out, the root of the slowness seems to be apparent sluggish IO in the form of slow disk and network access. Actions such as checking out many code files, restoring NuGet dependencies for .NET apps, downloading and installing tools, builds that involve reading and writing lots of files, tend to run a bit slow on the Windows runners.
Of course, we can point out that perhaps these runners are running on slower hardware. GitHub, according to their Actions documentation hosts their runners in Microsoft Azure. GitHub used to list the virtual machine SKUs used for their runners, but it was removed as of this commit in early 2025.
At the time of most of the slowness, GitHub used Dadsv5
series virtual machines. These general-purpose VMs feature the following specs relevant to running CI/CD workloads, depending on whether you use public (4 vCPU, 16GB RAM), private (2 vCPU, 8GB RAM) or organizational repository runners:
- vCPUs: 2-4
- RAM: 8-16 GB
- OS (Remote) Disk (C:) Throughput: 3750-6400 IOPS
- Temp (Local) Disk (D:) Throughput: 9000-19000 IOPS
A key feature of these VMs is the presence of a temporary disk. Data stored on this disk might be lost during hardware maintenance events, reboots, redeployment of VMs, and so on. Intuitively, these temporary disks are ideal due to their faster performance characteristics and the fact that GitHub Actions runners are by their nature ephemeral.
So, where am I going with this? GitHub Actions hosted runner Windows runner images are preinstalled with lots of software, such as Visual Studio Enterprise, .NET Framework, and Node.js to name a few. These components are typically installed to the C: drive, which is distinct from temporary storage on the D: drive. For the Dadsv5
SKU, this means the Windows OS and many installed software components are stored on slower, more high-latency managed disks backed by remote block blob storage*, meaning intensive IO workloads such as CI/CD are best kept on temporary storage (D:) if possible!
* Note: I'm seeking a reputable source or deep dive write-up that peeks under the hood of how Azure VM managed disks work under the hood with respect to remote block-blob storage. If you know of any, please feel free to reach out!
So, what can we do about this? (Easy Wins)
Move I/O heavy workloads to the D: drive
Luckily, many development tools such as NuGet and the .NET CLI expose some environment variables to tweak where their respective IO-heavy workloads occur. You may find specifying these in your GitHub Actions YAML workflows to use the D: drive where possible will result in a noticeable speed up.
Environment Variable | Description | Example Value |
---|---|---|
DOTNET_INSTALL_DIR | Specifies a directory where .NET SDKs should be installed by the action | D:\tools\dotnet |
DOTNET_ROOT | Specifies the location of .NET runtimes | D:\tools\dotnet |
NUGET_PACKAGES | Configures a path to the NuGet global-packages folder |
D:\.nuget\packages |
By default at the time of this writing, .NET is installed to the C: drive on Windows runners. At the expense of re-downloading and installing the .NET SDK installation to D: (20-30 seconds?), you may find a modest speed boost for building or publishing .NET projects due to SDK components (think MSBuild targets, .NET binaries, and so on) being more quickly accessed on a faster disk. It's necessary to test both approaches, the default and overridden environment variables, to see if your workflows benefit in terms of running time.
Some real-world examples such as this one by GitHub user jetersen show as much as 86% percent faster .NET CI times on Windows runners by overriding these environment variables. It's still notably slower than using a Linux runner, but I'll take it!
Reduce I/O Burden for .NET Framework (msbuild.exe) Builds
If you're working with some legacy .NET Framework builds with msbuild.exe
included with Visual Studio, you'll unfortunately find necessary components to build these solutions are tightly coupled with the .NET Framework, which is also unfortunately tightly coupled to the Windows operating system files on the C: drive. This includes accessing stuff like the Windows registry, GAC (Global Assembly Cache), or pre-compiled images stored on disk (NGEN'd images).
However, there are some overrides you may wish to try and speed up .NET Framework builds in the form of environment variables and MSBuild properties.
Reduce I/O (at the reasonable trade-off of increased CPU and memory utilization) by disabling loading pre-compiled images from disk
Environment Variable | Description | Value |
---|---|---|
COMPlus_ReadyToRun | When set to 0 , disables the use of pre-compiled images, forcing the runtime to JIT compile framework code |
0 |
COMPlus_ZapDisable | When set to 1 , it disables loading of NGen images (native images generated by ngen.exe). Most relevant to .NET Framework |
1 |
Both variables effectively trade disk I/O for CPU cycles by forcing runtime compilation instead of loading pre-compiled native code from the laggy OS disk. This may be beneficial given disk I/O is a primary bottleneck for many builds on Windows runners. It's worth testing to see if it results in a speed boost if your Windows builds are otherwise still too slow!
So, what else can we do? (Harder wins)
Windows Dev Drive / Virtual Hard Disk (VHDX)
With Dev Drive becoming a native feature of Windows Server 2025, this presents an opportunity to address some concerns around I/O performance.
In this GitHub issue comment I left back in November 2024, there may be significant I/O performance gains to be had as a result of reading/writing from a virtual hard disk drive using either NTFS or ReFS as file-system options.
I've benchmarked the I/O performance of
C:
,D:
, and ReFS-based virtual hard disk drives (E:
) onwindows-2022
runners, and the performance differences (IOPS) are quite remarkable (30X faster ‼️):
💾 Drive 🚀 IOPS C: 4,261.64 D: 83,587.64 E: (C:\dev_drive.vhdx) 127,822.75 E: (D:\dev_drive.vhdx) 124,335.26
Luckily, GitHub user samypr100 made a robust, reusable GitHub Action that makes setting up a Dev Drive quite easy. I recommend checking it out!
- uses: samypr100/setup-dev-drive@v3
RAM Disk
GitHub hosted runners are ephemeral by their nature and benefit from fast storage. RAM Disk to the rescue! Well, maybe, but proceed with caution and be prepared for things to break or just plain not work.
Is RAM Disk still a thing on Windows? For GitHub Actions Windows runners, kind of! I explored this for myself using the ImDisk virtual disk driver tool for Windows, which supports mounting available memory space as a lettered storage drive within Windows. If your I/O constrained GitHub Actions job's footprint is small enough to fit within 8-16GB of memory, super-fast read/write speeds you'll see from RAM-based storage might indeed speed things up.
I created a reusable GitHub Action that simplifies setting up a RAM-based storage drive. It's usage is straightforward:
- name: Setup RAM Disk
uses: chad-golden/setup-ramdisk@v1
with:
size-in-mb: 2048 # Optional: Default is 2048
drive-letter: 'R' # Optional: Default is R
copy-workspace: true # Optional: Default is false
This action will make it so that you'll have a RAM-based storage drive (by default the R: drive). This will even copy your checked-out code files from the runner's GitHub workspace. This combined with overriding DOTNET_
and NUGET_
environment variables referenced earlier in this article can be beneficial for speeding up I/O constrained builds.
For fun, here are some benchmarks to compare the speed of C: (OS), D: (temp), and R: (RAM disk), on a day where Windows decided to run particularly slow in the I/O department. Taken using Windows Server 2022 GitHub Hosted Runner (2 CPUs, 7GB RAM) circa November 2024:
Drive | IOPS/sec |
---|---|
C: (OS) | 136.34 |
D: (temp) | 8,246.81 |
R: (RAM disk) | 238,678.40 |
Anything else?
As I'm running out of time to write this deepish-but-only-scratching-the-surface dive into getting Windows runners to run a bit faster in GitHub Actions, there's certainly more techniques to explore in this space. Some I've tried before and would be interested in writing about are:
- Using the VHDX or the RAM Disk approach to cache and reuse tools and dependencies (such as NuGet or NPM packages) across runs without having to redownload and reinstall everything to the D: drive
- Advancements and features GitHub makes to accommodate faster hosted Windows builds, such as custom build images or more advanced disk hardware selections
- Advancements and features offered by Microsoft Azure to increase disk performance, such as V2 storage options, Ultra disks, or ephemeral OS disks
- Using WPR to perform some root cause analysis on disk, CPU, and processes that may be contributing to than slower-than-desired build runtimes
- Using some parallelism techniques, such as this reusable GitHub Action I made to parallelize and visualize
pwsh
-based scripts at the job step level in GitHub Actions
That's all for now. Happy building! 👋