Skip to content

A Windows build machine fills faster than any laptop

Workspaces, caches, container images and artefacts on a runner. What to prune automatically rather than by hand.

5 min read

A Windows machine used for builds does the work of every developer on the team plus every branch they abandoned. The good news is that it fills in entirely predictable ways, which makes it automatable.

The four things that grow

WhatWhyPrune with
Workspaces per jobOne checkout per job, kept afterwardsA cleanup step or retention setting
Package cachesRestored per job, rarely evictedA size and age policy
Container imagesPulled per builddocker system prune on a schedule
Artefacts and logsKept in case someone downloads themServer side retention settings

Measure before automating

Get-ChildItem C:\actions-runner\_work,C:\agent\_work,C:\Jenkins\workspace -Directory -EA SilentlyContinue |
  ForEach-Object {
    $s = (Get-ChildItem $_.FullName -Recurse -Force -File -EA SilentlyContinue | Measure-Object Length -Sum).Sum
    [PSCustomObject]@{ GB = [math]::Round($s/1GB,2); Path = $_.FullName }
  } | Sort-Object GB -Descending | Select-Object -First 15

The point of measuring first is to find which of the four is actually your problem. Pruning caches that are doing their job makes every build slower for no benefit.

Workspaces from branches that no longer exist

Get-ChildItem C:\actions-runner\_work -Directory -EA SilentlyContinue |
  Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } |
  Select-Object LastWriteTime, FullName

A workspace per branch, kept after the branch is deleted, is the same orphan problem as everywhere else. Sorting by last write date finds them immediately.

The Windows specific costs

  • Path length. Deep checkouts on a runner hit the 260 character limit, and enabling long paths is worth doing on every build machine.
  • Defender scanning. Every file written by a build is scanned, which costs real time. Excluding the work folder is standard practice on a dedicated runner.
  • The component store. A long lived runner accumulates superseded packages like any Windows machine, and a quarterly DISM cleanup belongs in the maintenance job.

The prune that is almost always right

docker builder prune -f --filter 'until=168h'
docker image prune -a -f --filter 'until=336h'

Build cache older than a week and images unused for a fortnight are safe on a build machine by definition, because anything still needed would have been touched.

Common questions

Why does a Windows CI runner fill up so fast?

It keeps a workspace per job, restores package caches for every build, pulls container images and stores artefacts. All four accumulate and none are cleaned by default.

What should I prune on a Windows build machine?

Docker build cache older than a week and images unused for a fortnight are almost always safe. Workspaces for branches that no longer exist are next, found by last write date.

Should I exclude the build folder from Defender?

On a dedicated runner, yes. Every file a build writes is scanned otherwise, which costs real time. On a shared machine it is a security decision rather than a performance one.

Do Windows runners hit the path length limit?

Yes, on deep checkouts. Enabling long path support is worth doing on every Windows build machine before it causes a confusing failure.

Read next