Why Does Docker Build Run Out of Disk?
One runner volume holds the checkout, the pulled base images, every intermediate stage, and the exported cache at once. Move the build to a remote builder.
A Docker build runs out of disk because one runner volume holds the repository checkout, every base image pulled by the Dockerfile, every intermediate stage of a multi-stage build, the final image, and the exported build cache at the same time. BuildKit keeps the intermediate stages in its cache until the build ends rather than freeing each stage as it finishes, so a build that produces a 200 MB image can still need tens of gigabytes of working space while it runs (Docker multi-stage build documentation).
Answer
The runner volume is a single shared working space and the build fills it from several directions at once. Here is what a single image-building job holds at its peak, with example sizes you would replace with your own measurements:
| What holds disk during the build | Where it lives | Example size |
|---|---|---|
| Repository checkout | Runner work directory | 3 GB |
| Base images pulled by the Dockerfile | Compressed blob plus extracted layer in the image store | 12 GB |
| Builder stage layers: toolchain, source tree, object files | BuildKit cache | 34 GB |
| Final stage layers | Image store | 4 GB |
Exported cache with mode=max | Staged on local disk before upload | 30 GB |
| A second platform in a multi-platform build | Duplicates the stage and image rows | 38 GB |
| Peak | 121 GB |
Every Linux x64 and Linux ARM64 label carries 150GB SSD, every Windows label carries 256GB SSD, and macOS carries 120GB on the 6 vCPU sizes and 270GB on the 12 vCPU sizes (cloud runners documentation, checked on 2026-08-13). The 121 GB peak above fits inside a Linux label with room to spare, and it does not fit inside a standard GitHub-hosted runner, which GitHub documents at 14 GB of SSD storage in the hosted runners reference, checked on 2026-08-13. Part of the volume is already occupied by the preinstalled tooling that ships in the runner image, catalogued in the public actions/runner-images repository, so budget against measured free space rather than the headline number.
Three fixes address the problem, in the order worth trying:
- Move the build to a remote Docker builder. The layers, the build cache, and the intermediate stages land on a dedicated builder virtual machine instead of the runner, and with
push: truethe image goes from the builder to the registry without ever touching the runner disk (Docker builders documentation). - Prune between stages. Drop the heavy stage and its cache once the artifact you need has been copied out. This buys space back within the job and gives up the layer reuse the next build would have had.
- Move to a label with a bigger volume. Disk does not scale with vCPU on Linux, so this means a different platform or a BYOC instance with local NVMe rather than a bigger Linux size.
Per-minute rates for every label and builder profile are on the pricing page.
Detail
Why a multi-stage build holds every stage until the end
A multi-stage Dockerfile reads as though the builder stage disappears when the final stage copies its output. On disk it does not. BuildKit records the result of each stage in its cache so that a later build, or a later --target, can reuse it, and that cache lives on the same volume as everything else until the job ends or something prunes it (Docker multi-stage build documentation).
Two multipliers turn that into a failure. A base image occupies disk twice, once as the compressed blob that was pulled and once as the extracted layer in the overlay store. And cache-to: type=gha,mode=max exports every intermediate layer, which means the build writes a second full copy of the stage output to local disk before uploading it. A multi-platform build repeats both for every platform in the list.
The command that shows the real split is docker system df, which breaks usage into images, containers, local volumes, and build cache. Run it after the failing step and the largest line names the fix.
Fix 1: run the build on a remote Docker builder
Remote Docker builders are one part of the WarpBuild surface alongside snapshot runners, CI observability, an MCP server, and the Action Debugger. Each builder profile is a dedicated build virtual machine with a persistent layer cache on its own local disk, and the runner keeps only the checkout and the build context it sends over (Docker builders documentation).
name: image
on:
push:
branches: [main]
jobs:
build:
runs-on: warp-ubuntu-latest-x64-4x
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push from the builder
uses: Warpbuilds/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
profile-name: "release-builder"Two details decide whether the runner disk stays clear. push: true sends the image from the builder to the registry directly, so no layer is written on the runner. Setting load: true instead copies the finished image back into the runner's image store, which puts the problem back where it started. The cache-from and cache-to lines that pointed at type=gha can also come out, because the profile keeps its cache on the builder rather than exporting it.
Builder profiles carry their own volumes, and the size is chosen with the profile (Docker builders documentation):
| Builder profile | Disk | Per minute |
|---|---|---|
| 16 vCPU, 32 GB | 100GB | $0.06 |
| 32 vCPU, 64 GB | 200GB | $0.12 |
| 64 vCPU, 128 GB | 200GB | $0.24 |
| 96 vCPU, 192 GB | 600GB | $0.36 |
| 96 vCPU, 192 GB | 2TB | $0.52 |
| 192 vCPU, 384 GB | 600GB | $0.72 |
| 192 vCPU, 384 GB | 2TB | $0.88 |
Both resources bill while the job runs, because the runner waits on the builder. A six minute build on the 16 vCPU, 100GB profile costs 6 x $0.06 = $0.36 in builder time plus 6 x $0.008 = $0.048 of warp-ubuntu-latest-x64-4x time, or $0.408 per build. Three hundred builds a month is $122.40. The Docker builds on GitHub Actions solution page works through profile sizing against matrix width and how parallel jobs share one billing session.
Fix 2: prune between stages
When the build has to stay on the runner, free the stage output as soon as its artifact has been copied out:
- name: Build the toolchain stage
run: docker build --target builder -t app-builder .
- name: Report disk before pruning
run: |
df -h /
docker system df
- name: Drop the toolchain stage
run: |
docker image rm app-builder
docker builder prune --forcedocker builder prune clears the BuildKit cache, which is normally the largest line in docker system df on a multi-stage build. The trade is explicit: the next run rebuilds those layers from scratch, so the job gets its disk back and loses its cache. That trade is what the remote builder route avoids, since the cache lives on a machine with room for it. The guide to an out-of-disk GitHub Actions runner covers the non-Docker sources of the same failure.
Fix 3: move to a label with a bigger volume
Disk is flat across Linux sizes. warp-ubuntu-latest-x64-2x and warp-ubuntu-latest-x64-32x both mount 150GB SSD, so resizing from 4 vCPU to 16 vCPU shortens the build without changing the space available to it (cloud runners documentation). The volume changes when the platform changes: 256GB on Windows labels, 270GB on warp-macos-26-arm64-12x. Runner storage is ephemeral and is deleted when the runner terminates, so nothing carried over between jobs is holding the space either. On BYOC, instance types that ship local NVMe SSDs have those disks detected, formatted, and mounted at the runner work directory at boot on Linux images (local SSD documentation), which is the largest volume available without leaving your own cloud account. Disk sizes for every runner label are set out in how much disk GitHub Actions runners have.
Related Questions
Does a larger runner size give me more disk for a Docker build?
Not on Linux. Every WarpBuild Linux x64 and Linux ARM64 label carries 150GB SSD from 2 vCPU up to 32 vCPU, so a size bump buys vCPU and RAM and leaves the volume unchanged (cloud runners documentation). Windows labels carry 256GB and warp-macos-26-arm64-12x carries 270GB, and the builder profile is the lever that actually moves disk.
Which prune command frees the most space during a Docker build?
Usually docker builder prune, because the BuildKit cache holds every intermediate stage the build produced. Run docker system df first to see the split between images, containers, local volumes, and build cache, then prune the largest line. Pruning the build cache also discards the layers the next build would have reused.
Does a remote Docker builder remove the disk ceiling completely?
No, it moves the ceiling to the 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), and the runner keeps only the checkout and the build context as long as the image is pushed from the builder rather than loaded back with load: true.
Pick a profile size from the remote Docker builder catalog, price the change against your own build minutes on the pricing page, and read the Docker builds on GitHub Actions solution page for the full workflow swap.
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.