Self-hosted service
jlumbroso/free-disk-space avatar
jlumbroso/free-disk-space

free-disk-space: a GitHub Action that deletes the runner until it fits

:octocat:⚙️🗑️ A GitHub Action to free disk space on an Ubuntu runner.

730 stars124 forksUnknownMIT

At a glance

What is it?
free-disk-space is a GitHub Action that frees disk space on an Ubuntu runner by removing toolchains, package caches and pre-pulled Docker images, with per-category switches and a v2 release that made the destructive defaults explicit. It suits CI jobs that need room for a large build, and it is a set of deletions rather than a cleaner.
Who is it for?
free-disk-space is worth adding to a workflow whose build step fails for want of space, because the action is a single step, the space it reports is measured rather than estimated, and the v2 release went out of its way to document which defaults are safe and which are not. It is the wrong tool for a job that needs one specific toolchain, since a single misjudged switch deletes it silently, and it is the wrong tool outside GitHub Actions on Ubuntu.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 36 days ago.
What is it written in?
GitHub does not report a main language for this repository.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The action deletes, and that is the whole mechanism

free-disk-space is a customizable GitHub Action to free disk space on Linux GitHub Actions runners. The framing matters: this is not a cache that gets pruned, and it is not a cleanup that moves files out of the way. Every option removes something from the runner image, permanently, for the life of the job.

The scale is stated in the project description. On a typical Ubuntu runner, with all options turned on, or more precisely not turned off, this can clear up to 31 GB of disk space in about three minutes, and the longest phase is calling apt to uninstall packages. The use case given is needing a lot of disk space to run computations.

So the audience is a CI pipeline that has hit the disk ceiling. A job that downloads a large dataset, a container image build with many layers, a Rust or Node build with a fat cache directory, a test matrix that compiles the world. In those cases the alternative is a larger and more expensive runner, and this action is a way to stay on the standard runner size.

The repository itself is small: action.yml, a docs/ directory, a LICENSE, a README and a CONTRIBUTING.md, plus a .github/ directory. The primary language is recorded as unknown, which is consistent with a composite or script action whose logic lives in shell commands inside action.yml rather than in a compiled program. The licence is MIT.

The example workflow, and the two families of option

The README's example is the shape you would copy into a workflow:

yaml
name: Free Disk Space (Ubuntu)
on: push

jobs:
  free-disk-space:
    runs-on: ubuntu-latest
    steps:

    - name: Free Disk Space (Ubuntu)
      uses: jlumbroso/free-disk-space@main
      with:
        android: true
        dotnet: true
        haskell: true
        large-packages: true
        docker-images: true

        preinstalled-runtimes: false
        swap-storage: false

The project ships the snippet with its own comments, and those comments are the design in two lines. Five options default to true, and you turn off whatever you turn out to need. Two default to false, and the reason given is that their failures are hard to trace back to this action.

The reason the true-defaults are safe is explained later and it is a reasonable argument. The assumption is that if you are reaching for this action at all you are already out of disk, and the expensive mistake is reclaiming too little. So the defaults delete as much as they safely can, and you turn off what you turn out to need, which works precisely because those failures tell you what they are.

One more mechanism is worth knowing before you configure anything. Specific options override general ones, so dotnet: false keeps .NET even where large-packages: true would otherwise remove it, and the run logs a notice when an exemption fires so you can see it happen. That is a fix for the v1 behaviour and the subject of a separate issue in the repository.

What each switch deletes, and how the failure shows up

The FAQ table is the most useful part of the documentation, because it pairs each option with the symptom you get if you needed the thing it removed.

android removes the Android SDK and NDK, and the symptom is that your Android build cannot find the SDK. dotnet removes the .NET SDK directory, and dotnet is simply not on PATH. haskell removes the GHC toolchain, so ghc and stack are not on PATH. Those three fail directly and name the missing thing.

large-packages is the broad one. It removes azure-cli, Chrome, Firefox, PowerShell, mono, LLVM, PHP, MongoDB, MySQL, and the Google Cloud SDK and CLI, plus the dotnet-* and aspnetcore-* packages unless dotnet is false. The symptom is that the command is not found. It is also the slow one, taking several minutes.

docker-images removes pre-pulled Docker images, and the symptom is direct-ish rather than direct: your job re-pulls images it expected to be cached, so it is slower rather than broken.

The two marked indirect are the ones to read twice, and they are exactly the two that default to false. preinstalled-runtimes removes the pre-cached Node, Go, Python and Ruby toolchains that the actions/setup-* steps use, installed in the path given by the AGENT_TOOLSDIRECTORY environment variable. The symptom is that actions/setup-python and friends still work, but they download their runtime instead of finding it, so it is usually just slower, and occasionally a pinned version is not available to download. swap-storage removes the runner's swap file, and it is described as the one to read twice.

The swap default and the preinstalled runtimes default exist because of one class of failure

The two options that default to false default to false for the same underlying reason, and the documentation states it plainly: because neither fails in a way you can trace back here.

Removing swap produces a job that dies under memory pressure. There is no error about swap, no mention of this action, just a runner that stopped. The linked issue records that a contributor lost a couple of days working out that this action was the cause, on the grounds that a job short on disk is often short on memory too. That is described as too high a price for a default.

preinstalled-runtimes is the same shape in a milder form, and has defaulted to false since it was added in 2022 for the same reason.

The decision to enable them is therefore an explicit request, and the snippet for it is deliberately minimal:

yaml
    - uses: jlumbroso/free-disk-space@main
      with:
        swap-storage: true

The reasoning and the point at which it stops applying are written up as architecture decision records in the repository, at docs/adr/0001 for why the action exists and why it is public, and docs/adr/0007 for the policy acquiring a boundary around the swap default. That a small utility action keeps ADRs for its own defaults is a sign of how carefully the v2 release was made.

v2 renamed one option, changed one default and fixed one bug

The upgrade notes are short enough to be worth reading in full, because each bullet names the reason rather than just the change.

Most workflows need no change at all, which is the first line and the most important one.

If you set tool-cache:, rename it to preinstalled-runtimes:. The old name still works and will keep working, but it prints a deprecation warning, and the new name says what is removed rather than where it is cached. The project explains the rename in the options section too, and states that the old name will keep working until v3.0.0.

If you relied on swap being removed by default, add swap-storage: true. It now defaults to false, because removing swap can kill a job under memory pressure with no error pointing back to the action, which is issue 12 in the repository.

If you set dotnet: false and were surprised that .NET was still removed, that is fixed, and the mechanism explains why it happened. Two different options deleted .NET by two different mechanisms: dotnet removed the SDK directory at /usr/share/dotnet, while large-packages removed the apt packages dotnet-* and aspnetcore-*. With large-packages true, which is the default, setting dotnet false could not keep .NET. Now the specific option overrides the general one, so dotnet false exempts .NET from the large-packages sweep as well, and the run logs a notice saying so. That is issue 33 in the repository.

The release tags carry the same discipline. v2.0.0 is annotated as three breaking changes, each with its reason, and v1.3.2 is annotated as a security fix and the decision records, both dated 2026-09-17, the same day as the last push to the repository.

Where free-disk-space is the wrong tool

The first limit is the one the documentation itself insists on. Every option deletes things, and two of them produce failures that do not name the missing piece, so a job can die without any trace back to this action. The FAQ is explicit that those are the ones worth knowing about before you turn them on, and the swap case has already cost one contributor days of debugging.

The second limit is that the action is a blunt instrument. The options are categories, not selectors. You cannot keep Chrome and drop Firefox, you cannot keep the Google Cloud SDK while dropping azure-cli, and large-packages is one switch that takes a fixed list. A workflow that needs one tool out of a category must turn the whole category off and find another way to get that one thing back.

The third is that it is Ubuntu and GitHub Actions specific. The action targets Linux GitHub Actions runners, and the options map to the layout of that image, including the AGENT_TOOLSDIRECTORY path convention that the setup-* actions use. It does nothing for a macOS or Windows runner, and it does nothing outside a GitHub Actions job.

The fourth is that it does not address the real problem. If a build needs 40 GB of space, deleting 31 GB of preinstalled tools may still not be enough, and if a job grows over time it will keep hitting the ceiling. The GitHub-hosted runner disk size is not configurable from inside a workflow, and self-hosted runners with more space are the other answer to the same complaint. The action is a way to postpone that decision, not to avoid it.

The fifth is the trust model. It deletes parts of the runner image that later steps depend on, and the reporting of freed space is per category, which is helpful for diagnosis but not a substitute for reading the list of what is removed before you run it.

Editorial conclusion

free-disk-space is worth adding to a workflow whose build step fails for want of space, because the action is a single step, the space it reports is measured rather than estimated, and the v2 release went out of its way to document which defaults are safe and which are not. It is the wrong tool for a job that needs one specific toolchain, since a single misjudged switch deletes it silently, and it is the wrong tool outside GitHub Actions on Ubuntu. Before you enable the two switches that default to false, read the FAQ rows for them: swap-storage removes the runner's swap file, which can kill a job under memory pressure with no error naming this action, and preinstalled-runtimes leaves the setup-python and setup-node actions working but downloading their runtime instead of finding it, occasionally failing outright when a pinned version is no longer available.

Frequently asked questions

How do I use free-disk-space in a GitHub Actions workflow?

Add a step that uses jlumbroso/free-disk-space@main on an ubuntu runner, with the category switches you want under the with key. The options android, dotnet, haskell, large-packages and docker-images default to true, while preinstalled-runtimes and swap-storage default to false.

Why does swap-storage default to false?

Because removing swap makes a job die under memory pressure with no error naming this action, just a runner that stopped, and a job that is short on disk is often short on memory too. The documentation says that cost one contributor a couple of days to diagnose, which was too high a price for a default, so you have to ask for it explicitly.

What changed in version 2 of the free-disk-space action?

Three breaking changes, each with its reason. The tool-cache option was renamed to preinstalled-runtimes, with the old name still working and printing a deprecation warning until v3.0.0. swap-storage now defaults to false instead of removing swap by default. And a specific option now overrides a general one, so dotnet: false keeps .NET even when large-packages: true is set.

Official sources

  1. Issues
  2. jlumbroso/free-disk-space on GitHub
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jlumbroso-free-disk-space.svg)](https://hysenlabs.com/projects/jlumbroso-free-disk-space)

Was this useful?