GitHub Actions: Resolving ‘Runner Out of Disk Space’ Errors on Alpine Linux Builds
Troubleshoot and fix 'runner out of disk space' errors in GitHub Actions workflows, specifically when building applications on Alpine Linux. Optimize your CI/CD builds for efficiency.
Troubleshoot and fix 'runner out of disk space' errors in GitHub Actions workflows, specifically when building applications on Alpine Linux. Optimize your CI/CD builds for efficiency.
When running complex build processes within GitHub Actions, especially when targeting or utilizing Alpine Linux images, encountering "runner out of disk space" errors can be a frustrating hurdle. While Alpine is renowned for its minimal footprint, the accumulated artifacts, dependencies, and temporary files generated during a build can quickly exhaust the finite disk resources of a GitHub Actions runner, leading to abrupt workflow failures. This guide provides a highly technical, step-by-step approach to diagnose, understand, and resolve these disk space issues.
Symptom & Error Signature
The most common symptom is a GitHub Actions workflow step failing with an error indicating insufficient disk space. This can manifest in various ways, often with the process attempting to write data and failing due to a full filesystem.
Typical error messages you might observe in your workflow logs:
## Example 1: npm build failure
...
npm ERR! code ENOSPC
npm ERR! syscall write
npm ERR! path /__w/your-repo/node_modules/.bin/webpack
npm ERR! errno -28
npm ERR! No space left on device
npm ERR! A complete log of this run can be found in:
npm ERR! /home/runner/.npm/_logs/...
Error: Process completed with exit code 256.
...
## Example 2: Docker build failure
...
Error: failed to create task for build: failed to build LLB: disk usage limit exceeded
Error: The process '/usr/bin/docker' failed with exit code 1
...
## Example 3: General system error
...
/bin/sh: write error: No space left on device
df: /var/lib/docker: No space left on device
...
## Example 4: Output from a diagnostic step within the workflow
- name: Check Disk Space
run: df -h
...
Filesystem Size Used Avail Use% Mounted on
overlay 64G 64G 0 100% /
tmpfs 7.8G 0 7.8G 0% /dev
tmpfs 7.8G 0 7.8G 0% /sys/fs/cgroup
/dev/sda1 64G 64G 0 100% /__w/_temp/docker_build_home
tmpfs 7.8G 4.0K 7.8G 1% /tmp
...
The key indicators are "No space left on device," "ENOSPC," or "disk usage limit exceeded," often accompanied by a 100% Use% reported by df -h.
Root Cause Analysis
GitHub Actions runners, even the ubuntu-latest variant, operate with finite resources. Specifically, the ephemeral disk allocated to a job is typically around 14-64 GB (depending on the runner type and configuration, ubuntu-latest often presents ~64GB total with a significant portion allocated to the Docker daemon and pre-installed tools), which can be quickly consumed. When building on Alpine Linux, several factors contribute to this issue:
- Ephemeral Runner Limitations: The underlying GitHub Actions runner provides a limited disk. While 64GB might seem ample, the Docker daemon's storage, pre-installed software, and the base operating system consume a substantial portion, leaving less for your build.
- Verbose Dependency Management:
- Node.js:
node_modulesdirectories can easily grow to several gigabytes, especially with large frontend projects or numerous development dependencies. - Python: Pip caches (
~/.cache/pip) and large virtual environments can accumulate rapidly. - Java/JVM: Maven (
.m2directory) and Gradle caches can become massive over time. - Go: The Go module cache can also consume significant space.
- PHP: Composer's
vendordirectory and cache.
- Node.js:
- Inefficient Docker Builds:
- Non-multi-stage Dockerfiles: Building all dependencies and runtime components in a single Dockerfile stage leads to large intermediate layers that are not always effectively pruned.
- Lack of
.dockerignore: Copying unnecessary files (e.g.,node_modules,.git, temporary build artifacts from the host context) into the Docker build context. - Unoptimized
RUNcommands: Creating new Docker layers for each command, rather than chaining related commands to minimize layer count and intermediate file retention. - Docker Build Cache Bloat: The Docker daemon on the runner maintains its own layer cache, which can grow large if not managed.
- Temporary File Accumulation: Compilers, test runners, or specific build tools can generate large temporary files in
/tmpor within the project directory that are not automatically cleaned up. - GitHub Actions Cache Management: While the
actions/cacheaction is designed to save time, improperly configured or excessively large caches can indirectly contribute to disk pressure if they store vast amounts of data that are not efficiently reused or pruned.
Step-by-Step Resolution
Addressing disk space issues requires a multi-pronged approach focusing on minimizing consumption, aggressive cleanup, and optimizing build processes.
1. Diagnose Disk Usage During Workflow Execution
The first step is to pinpoint what is consuming the disk space. Add diagnostic steps to your workflow:
- name: Check Initial Disk Space
run: df -h
- name: List Top 20 Largest Directories in Repository
run: sudo du -sh $(find . -maxdepth 2 -type d) | sort -rh | head -n 20
working-directory: ${{ github.workspace }} # Adjust if your build directory is different
- name: Check Docker Image Disk Usage (if applicable)
run: docker system df -v # Or docker images --format "{{.Size}}t{{.Repository}}:{{.Tag}}" | sort -rh
# Run your actual build steps here
- name: Check Disk Space After Build
run: df -h
- name: List Top 20 Largest Directories After Build
run: sudo du -sh $(find . -maxdepth 2 -type d) | sort -rh | head -n 20
working-directory: ${{ github.workspace }}
Analyze the output of du -sh to identify unexpectedly large directories, such as node_modules, vendor, .m2, or target directories.
2. Implement Aggressive Cleanup Strategies
After build steps that generate large intermediate files or dependencies, immediately clean them up if they are no longer needed for subsequent steps or the final artifact.
- name: Build Node.js Application
run: |
npm install
npm run build --if-present
- name: Clean up Node.js modules (if not needed for deployment artifact)
if: success() # Only clean if build was successful
run: |
echo "Cleaning up node_modules..."
rm -rf node_modules
npm cache clean --force # Clear npm cache
echo "Disk space after cleanup:"
df -h
- name: Cleanup Python caches (if applicable)
if: success()
run: |
echo "Cleaning up pip cache..."
pip cache purge
echo "Disk space after cleanup:"
df -h
- name: Cleanup Maven/Gradle caches (if applicable)
if: success()
run: |
echo "Cleaning up Maven/Gradle caches..."
rm -rf ~/.m2/repository/**/*.jar.lastUpdated # Remove partial downloads
# For Gradle, consider `gradle cleanBuildCache` or similar if configured
echo "Disk space after cleanup:"
df -h
- name: Remove Alpine Linux package cache
if: success()
run: |
echo "Cleaning up Alpine apk cache..."
sudo apk cache clean
echo "Disk space after cleanup:"
df -h
- name: Prune Docker system if using Docker builds extensively
if: success() && always() # Always attempt to prune
run: |
echo "Pruning Docker system..."
docker system prune -af --volumes # Prune all unused Docker data aggressively
echo "Disk space after Docker prune:"
df -h
Be cautious when using
sudo rm -rf /tmp/*ordocker system prune -af. While effective, ensure you understand the implications and that you are not deleting files or containers required by other concurrent processes or subsequent workflow steps. In most GitHub Actions runner setups,/tmpis job-isolated, but Docker prune affects the daemon's global state.
3. Optimize Docker Builds (Crucial for Alpine-based images)
If your workflow involves building Docker images, optimizing your Dockerfiles is paramount.
a. Implement Multi-Stage Builds
Separate your build environment from your runtime environment. Alpine is excellent for the final runtime stage.
# Stage 1: Build Environment
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install # Install dev and runtime dependencies for build
COPY . .
RUN npm run build # Compile your application
# Stage 2: Runtime Environment (minimal Alpine)
FROM alpine:3.18
LABEL authors="Your Name <[email protected]>"
WORKDIR /app
# Install only runtime dependencies needed for the application to run
RUN apk add --no-cache nodejs
COPY --from=builder /app/dist ./dist
# If you have specific assets or other files to copy:
# COPY --from=builder /app/public ./public
EXPOSE 8080
CMD ["node", "dist/main.js"]
b. Utilize .dockerignore
Ensure your build context is as small as possible by excluding unnecessary files like node_modules (if installed inside the container), .git, dist, target, etc., from being copied to the Docker daemon.
# .dockerignore example
node_modules/
.git/
.gitignore
.DS_Store
dist/
build/
*.log
tmp/
c. Chain Commands and Use --no-cache
Minimize layers and intermediate file accumulation by chaining RUN commands and clearing caches immediately.
# Bad: creates multiple layers, intermediate cache
# RUN apk update
# RUN apk add some-package
# RUN rm -rf /var/cache/apk/*
# Good: single layer, clears cache
RUN apk update &&
apk add --no-cache some-package another-package &&
rm -rf /var/cache/apk/*
d. Leverage BuildKit for Efficient Caching
Use docker buildx build with GitHub Actions caching to cache Docker layers between workflow runs.
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: false # Set to true if pushing to a registry
tags: your-repo/image:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max,compression=zstd # or gzip, default is uncompressed
4. Leverage GitHub Actions Cache Effectively
Strategically cache language-specific dependencies to speed up builds and reduce repeated downloads. Be mindful of cache size.
- name: Cache Node.js modules
uses: actions/cache@v4
with:
path: ~/.npm # Cache npm global cache or node_modules directly
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install Node.js Dependencies
run: npm ci # Use npm ci for clean, reproducible installs
- name: Cache Python dependencies
uses: actions/cache@v4
with:
path: ~/.cache/pip # Cache pip's download cache
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install Python Dependencies
run: pip install -r requirements.txt
While caching reduces build time, it also consumes disk space. Ensure your cache keys are granular and invalidate correctly. Large, frequently changing caches can become counterproductive. GitHub Actions cache has a maximum size per repository (typically 10GB total), which is shared.
5. Minimize Repository Checkout Size
If your repository contains many large, irrelevant files for the build (e.g., historical data, large test datasets, unrelated projects in a monorepo), reduce the checkout depth.
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1 # Only fetch the latest commit, not full history
fetch-depth: 1can cause issues with tools that require full Git history (e.g.,git describe,semantic-release). Test thoroughly to ensure compatibility. For monorepos, considersparse-checkout.
6. Consider Self-Hosted Runners (Last Resort)
If all optimization efforts fail to meet your disk space requirements, or if you have highly specialized needs (e.g., very large artifact caching, massive temporary files), a self-hosted GitHub Actions runner offers full control over hardware resources, including disk space. This, however, shifts the operational burden to your team.
runs-on: self-hosted
This requires setting up and maintaining your own server or VM to act as a runner, which comes with its own costs and management overhead.
By systematically applying these strategies, you can effectively manage disk space on GitHub Actions runners, ensuring your Alpine Linux builds complete successfully and efficiently.
Our Production Verification Guarantee
Encountering a bug not covered here or running a non-standard kernel configuration? Our solutions are continually refined against real production incidents. Submit an environment trace for our editorial team to replicate.