No Space Left on Device in GitHub Actions
A no space left on device failure means the runner volume filled mid-job. Print usage at three points to name the consumer, then apply one of three fixes.
Last verified:
A GitHub Actions job that fails with No space left on device filled the volume that holds the runner work directory, and the step named in the log is whichever step happened to write last. The consumer is almost always one of four things sharing that volume: the checkout, restored caches, Docker image layers, or artifacts staged for upload, and the fix depends on which one it is.
Measuring takes two extra steps in the workflow. Every disk figure on this page comes from the cloud runners documentation, checked on 2026-08-13.
Diagnosis
The message is the kernel ENOSPC error passed through whatever tool was writing. Two separate resources can run out, and they need different commands:
- Free blocks.
df -h /shows used and available bytes on the volume that carries the work directory. - Free inodes.
df -i /shows the file count ceiling. A dependency tree with millions of small files can exhaust inodes whiledf -hstill reports free gigabytes, which is why a job can report no space with a disk that looks half empty.
Print both at three points so the growth is attributable rather than a guess. The first reading establishes the starting floor, because the preinstalled runner image already occupies part of the volume. The second isolates the checkout. The third runs with if: always() so it survives the failure that prompted the investigation.
name: build
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: warp-ubuntu-latest-x64-8x
steps:
- name: Disk at job start
run: |
df -h /
df -i /
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Disk after checkout
run: |
df -h /
du -xh --max-depth=1 "$GITHUB_WORKSPACE" | sort -h | tail -10
- name: Build the image
run: docker build -t app:${{ github.sha }} .
- name: Disk after the build
if: always()
run: |
df -h /
df -i /
docker system df
du -xh --max-depth=1 "$RUNNER_TEMP" | sort -h | tail -10Read the three readings as deltas, and each delta points at one consumer:
- Start to post-checkout. Repository history and LFS objects. A
fetch-depth: 0clone of a repository with binary history is the largest single fixed cost in most jobs. - Post-checkout to post-build, with
docker system dfshowing growth in images and build cache. Image layers. Every intermediate stage of a multi-stage build stays resident until the build ends, and each matrix tag adds another copy of the layers that differ. - Little block growth with
df -inear its ceiling. File count. Package stores that hard-link thousands of files per dependency hit this before they hit the byte limit. - A late spike after the build. Artifact staging.
actions/upload-artifactcompresses into a temporary path, so the tarball is a second copy of the data on the same volume.
Two path details save time on the way. Use $GITHUB_WORKSPACE and $RUNNER_TEMP instead of hardcoded paths, because Ubuntu 24.04 ARM64 runners set the work directory to /runner/_work rather than GitHub's /home/runner/work/. And runner storage is ephemeral and is deleted when the runner terminates, so every measurement has to happen inside the job. When the log still leaves the consumer unclear, the Action Debugger pauses the workflow and opens an SSH session on the runner, which is the fastest way to run du interactively against a filesystem that is about to disappear.
Set the ceiling you are measuring against from the catalog. The example workload sizes below are placeholders to replace with your own numbers, and the free-at-peak column assumes the whole volume is available, which the df -h reading at job start will correct downward.
| Placement | Volume | Checkout | Restored caches | Image layers | Staged artifacts | Free at peak |
|---|---|---|---|---|---|---|
| Linux x64, every size | 150GB | 12 GB | 20 GB | 45 GB | 5 GB | 68 GB |
| Linux ARM64, every size | 150GB | 12 GB | 20 GB | 45 GB | 5 GB | 68 GB |
| macOS, 6 vCPU labels | 120GB | 12 GB | 20 GB | none, macOS runners cannot run Docker | 5 GB | 83 GB |
| macOS, 12 vCPU labels | 270GB | 12 GB | 20 GB | none, macOS runners cannot run Docker | 5 GB | 233 GB |
| Windows, every size | 256GB | 12 GB | 20 GB | 45 GB | 5 GB | 174 GB |
The row that surprises teams is the flat one. Disk does not scale with vCPU on Linux or Windows: warp-ubuntu-latest-x64-2x and warp-ubuntu-latest-x64-32x both mount 150GB SSD, so answering a disk failure with a bigger label moves the job from $0.004 per minute to $0.064 per minute and leaves the ceiling where it was.
Fix
Three fixes, ranked by how much headroom they buy per unit of effort.
1. Move image builds to a remote Docker builder. Image layers are the fastest growing row in the table above, and a remote builder takes them off the runner volume entirely. The build runs on a dedicated builder virtual machine with a persistent layer cache, and builder profiles run from 100GB of disk on the 16 vCPU size up to 2TB on the 96 and 192 vCPU sizes (Docker builders documentation). The swap is one action and one input:
- name: Build and push
uses: Warpbuilds/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/acme/app:${{ github.sha }}
profile-name: "release-builder"Remove the docker/setup-buildx-action step if it only existed to set up a builder. After the swap the runner holds the checkout and the build context, and the layers live on the builder profile, where the persistent cache also survives between runs. The Docker builder catalog lists every profile with its disk and rate.
2. Prune between steps. When the build has to stay on the runner, reclaim space at the boundary between stages instead of at the end:
- name: Reclaim space before the test stage
run: |
docker image prune -af --filter "until=1h"
docker builder prune -af
rm -rf "$RUNNER_TEMP"/build-context
df -h /Pruning costs wall clock on every run, including the runs that were never close to the ceiling, which is why it ranks below moving the build off the runner. Reach for it when the pressure comes from a single stage whose output a later stage never reads.
3. Raise the ceiling where raising it is possible. On Linux and Windows labels there is no larger volume to move to, so this fix means something specific. On macOS, the 12 vCPU labels carry 270GB against 120GB on the 6 vCPU labels. On BYOC, the disk comes from the instance type in your own cloud account, and instance types that ship local NVMe SSDs have those devices detected, striped into RAID-0, formatted, and mounted to the runner work directory at boot with no configuration on Linux images (local SSD documentation). The local SSD runner page covers which instance families carry them.
Configuration
Once the consumer is known, these are the knobs that move it.
Checkout. fetch-depth: 1 is the default and the cheapest setting; full history belongs only in jobs that read it, such as tag calculation or changelog generation. Sparse checkout narrows a monorepo working tree to the paths the job builds. Both matter most on the repositories where this failure shows up first, and the large repository guide works through them.
Caches. Cache entries live off the runner, so only the restored copy occupies runner disk during the job. Restore the caches a job actually uses instead of a shared superset, and keep the restore paths out of directories that later steps recurse over.
Artifacts. Upload, then delete the local copy before the next heavy step, because compression writes a second copy into RUNNER_TEMP before the upload starts.
Matrix shape. Each matrix leg gets its own runner and its own volume, so splitting a job that builds five images sequentially into five legs divides the peak by five and shortens the critical path at the same time.
Snapshot runners. A snapshot captures the runner virtual machine mid-workflow so later jobs boot from that state. Capture the snapshot after the dependency warm-up and before the layers accumulate, since whatever occupies the disk at capture time occupies it again on every boot.
Labels. Pinning an image family changes the image and leaves the volume and the rate alone: warp-ubuntu-2604-x64-8x carries the same 150GB SSD at the same $0.016 per minute as warp-ubuntu-latest-x64-8x.
Cost or Time Model
A disk failure costs the minutes already burned plus the full re-run, so its price scales with how late in the job it lands. Take a repository that builds images on 500 runs a month, 18 minutes per run on warp-ubuntu-latest-x64-8x at $0.016 per minute, where 6 percent of runs die on disk at minute 16.
- Wasted minutes on the failed attempts: 30 runs x 16 minutes = 480 minutes, or $7.68.
- Re-run minutes: 30 x 18 = 540 minutes, or $8.64.
- Total waste: $16.32 a month, plus about 17 hours of developer wall clock spent waiting on doomed jobs and their retries.
Now price the two fixes that change the outcome. Rates from the pricing page, checked on 2026-08-13.
| Line | Prune between steps | Remote Docker builder |
|---|---|---|
| Runs per month | 500 | 500 |
| Added runner minutes | 375, at 45 seconds per run | 0 |
| Added runner cost | $6.00 at $0.016 per minute | $0.00 |
| Builder session minutes | 0 | 2,000, at 4 minutes per build |
| Builder cost | $0.00 | $120.00 at $0.06 per minute |
| Failure waste removed | $16.32 | $16.32 |
Pruning is the cheaper line and it taxes all 500 runs to protect the 30 that would fail. The builder profile costs more per month and buys a persistent layer cache and a volume that starts at 100GB, which is the reason to pick it when image layers are the growing row rather than a one-off.
The third option, paying for a bigger label, does nothing here. warp-ubuntu-latest-x64-16x costs $0.032 per minute against $0.016 and mounts the same 150GB. Against the hosted baseline the size you are already on is the cheaper seat: warp-ubuntu-latest-x64-8x (8 vCPU, 32 GB) costs $0.016 per minute against $0.022 per minute for the 8-core Linux larger runner (8 vCPU, 32 GB), which is 27 percent lower list price, with GitHub list prices checked on 2026-08-13 against the Actions minute multipliers reference.
Disk is one of several ceilings a slow or failing pipeline runs into; the guide to speeding up GitHub Actions covers runner sizing, caching, and the rest.
FAQ
What does no space left on device mean in GitHub Actions?
The kernel refused a write because the volume holding the runner work directory ran out of free blocks or free inodes. The step named in the log is whichever step happened to write last, so the step that filled the disk is usually an earlier one. Print df -h and df -i at three points in the job to find it.
How much disk do WarpBuild runners have?
Every Linux x64 and Linux ARM64 label carries 150GB SSD, every Windows label carries 256GB SSD, and macOS labels carry 120GB on the 6 vCPU sizes and 270GB on the 12 vCPU sizes. Remote Docker builder profiles run from 100GB up to 2TB. Figures from the cloud runners documentation, checked on 2026-08-13, and the disk space answer lists them label by label.
Does moving to a larger runner size give me more disk?
On Linux and Windows, no. Disk is flat across sizes, so warp-ubuntu-latest-x64-32x mounts the same 150GB as warp-ubuntu-latest-x64-2x and costs $0.064 per minute against $0.004. The macOS 12 vCPU labels do carry 270GB against 120GB, and on BYOC an instance type with local NVMe SSDs raises the ceiling. When Docker layers are the cause, why Docker build runs out of disk covers the build-side fixes.
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.