All Articles

GitHub Actions 'No Space Left on Device': How to Fix Runner Disk Issues

GitHub Actions failing with 'no space left on device'? Here's how to free disk space on runners, optimize Docker builds, and handle large monorepos.

DevOpsBoysMar 20, 20265 min read
Share:Tweet

Your CI/CD pipeline was working fine for months. Then one day: no space left on device. Docker builds fail. npm install crashes. Artifact uploads time out. The GitHub-hosted runner ran out of disk space.

This is becoming the most common GitHub Actions failure for teams with Docker-heavy pipelines and growing codebases. Here's how to fix it permanently.

Why Runners Run Out of Space

GitHub-hosted runners (ubuntu-latest) come with approximately 14GB of free disk space. That sounds like a lot until you realize the runner also includes:

  • 8GB+ of pre-installed tools (Android SDK, .NET, Haskell, etc.)
  • Docker images cached from previous layers
  • Your repository checkout
  • Build artifacts and node_modules

A typical pipeline that builds a Docker image, runs tests, and pushes to a registry can easily consume 10-15GB.

Fix 1 — Free Space by Removing Pre-installed Software

The nuclear option — remove software you don't need. This is the most effective fix:

yaml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - name: Free disk space
      run: |
        sudo rm -rf /usr/share/dotnet
        sudo rm -rf /opt/ghc
        sudo rm -rf /usr/local/share/boost
        sudo rm -rf "$AGENT_TOOLSDIRECTORY"
        sudo rm -rf /usr/local/lib/android
        sudo rm -rf /opt/hostedtoolcache
        sudo docker system prune -af
        df -h

This typically frees 10-15GB of disk space. The df -h at the end shows how much space you recovered:

Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        84G   24G   57G  30% /

Use a Community Action (Cleaner Approach)

yaml
    - name: Free disk space
      uses: jlumbroso/free-disk-space@main
      with:
        tool-cache: true
        android: true
        dotnet: true
        haskell: true
        large-packages: true
        docker-images: true
        swap-storage: true

This action handles cleanup comprehensively and is maintained by the community.

Fix 2 — Use /mnt for Docker Builds

The runner's /mnt directory has additional space. Move Docker's storage there:

yaml
    - name: Move Docker data to /mnt
      run: |
        sudo systemctl stop docker
        sudo mv /var/lib/docker /mnt/docker
        sudo ln -s /mnt/docker /var/lib/docker
        sudo systemctl start docker
        docker info | grep "Docker Root Dir"

This gives Docker access to the full /mnt partition, which typically has 50GB+ of free space.

Fix 3 — Optimize Docker Builds

Use Multi-Stage Builds

Multi-stage builds keep only the final artifacts, dramatically reducing intermediate layer sizes:

dockerfile
# Stage 1: Build (large, temporary)
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
 
# Stage 2: Production (small, final)
FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]

The builder stage might use 2GB. The final image is 200MB.

Use .dockerignore

Prevent unnecessary files from being sent to the Docker daemon:

node_modules
.git
*.md
.env*
test/
coverage/
.next/
dist/

Build with --no-cache When Needed

yaml
    - name: Build Docker image
      run: docker build --no-cache -t my-app:${{ github.sha }} .

Cached layers consume disk space. When space is tight, skip the cache.

Fix 4 — Clean Up Between Steps

If your workflow has multiple Docker-heavy steps, clean up between them:

yaml
    - name: Build and test
      run: |
        docker compose up -d
        npm run test:integration
        docker compose down --volumes --rmi all
 
    - name: Build production image
      run: |
        docker build -t my-app:latest .
        docker push my-app:latest
        docker rmi my-app:latest

Key commands:

  • docker compose down --volumes --rmi all — removes containers, volumes, AND images
  • docker system prune -af — removes everything unused
  • docker rmi $(docker images -q) — removes all images

Fix 5 — Handle Large Repositories

For monorepos, a full git clone can consume gigabytes:

yaml
    - uses: actions/checkout@v4
      with:
        fetch-depth: 1    # Shallow clone — only latest commit
        sparse-checkout: |
          src/
          package.json
          Dockerfile
  • fetch-depth: 1 — only clones the latest commit, not full history
  • sparse-checkout — only clones specific directories (huge savings for monorepos)

Fix 6 — Manage Artifacts Efficiently

Compress Before Uploading

yaml
    - name: Upload artifacts
      uses: actions/upload-artifact@v4
      with:
        name: build-output
        path: dist/
        compression-level: 9
        retention-days: 3    # Don't keep forever

Download Only What You Need

yaml
    - name: Download artifact
      uses: actions/download-artifact@v4
      with:
        name: build-output
        path: dist/

Clean Up Old Artifacts

Add a cleanup job at the end of your workflow:

yaml
  cleanup:
    runs-on: ubuntu-latest
    needs: [build, test, deploy]
    if: always()
    steps:
    - uses: geekyeggo/delete-artifact@v4
      with:
        name: build-output

Fix 7 — Cache Smartly

npm/yarn Cache (Don't Cache node_modules)

yaml
    - uses: actions/setup-node@v4
      with:
        node-version: 20
        cache: 'npm'    # Caches ~/.npm, not node_modules

Docker Layer Cache with BuildKit

yaml
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v3
 
    - name: Build with cache
      uses: docker/build-push-action@v5
      with:
        push: true
        tags: my-app:latest
        cache-from: type=gha
        cache-to: type=gha,mode=max

GitHub Actions cache (type=gha) stores Docker layers in the Actions cache instead of on disk.

Fix 8 — Use Larger Runners

If you consistently need more space, use larger runners:

yaml
jobs:
  build:
    runs-on: ubuntu-latest-16-cores  # 16 CPU, 64GB RAM, more disk

Or self-hosted runners with custom disk sizes:

yaml
jobs:
  build:
    runs-on: self-hosted

Self-hosted runners give you full control over disk size, Docker cache, and pre-installed tools.

Monitoring Disk Usage

Add disk monitoring to catch issues before they crash your pipeline:

yaml
    - name: Check disk space
      run: |
        echo "=== Disk Usage ==="
        df -h /
        echo ""
        echo "=== Docker Usage ==="
        docker system df
        echo ""
        echo "=== Largest Directories ==="
        du -sh /home/runner/work/* 2>/dev/null | sort -rh | head -10

Add this after each major step to identify which step consumes the most space.

Complete Optimized Workflow

Here's a complete workflow with all optimizations applied:

yaml
name: Build and Deploy
on:
  push:
    branches: [main]
 
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - name: Free disk space
      uses: jlumbroso/free-disk-space@main
      with:
        tool-cache: true
        android: true
        dotnet: true
        haskell: true
        large-packages: true
 
    - uses: actions/checkout@v4
      with:
        fetch-depth: 1
 
    - uses: docker/setup-buildx-action@v3
 
    - uses: docker/login-action@v3
      with:
        registry: ghcr.io
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}
 
    - uses: docker/build-push-action@v5
      with:
        push: true
        tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
        cache-from: type=gha
        cache-to: type=gha,mode=max
 
    - name: Final disk check
      if: always()
      run: df -h /

Quick Reference

ProblemSolutionSpace Saved
Pre-installed toolsRemove dotnet, android, haskell10-15GB
Docker data dir fullMove to /mnt30-50GB available
Large Docker imagesMulti-stage builds50-80%
Full git historyfetch-depth: 11-5GB
Docker layer cacheUse GHA cache, not disk2-8GB
Old artifactsSet retention-days, delete after1-5GB

Wrapping Up

"No space left on device" is a solvable problem. Start with removing pre-installed software (Fix 1) — that alone solves 80% of cases. Then optimize your Docker builds and add cleanup steps.

The key principle: GitHub runners are ephemeral. Don't try to cache everything — cache strategically and clean aggressively.

Want to master GitHub Actions and CI/CD pipelines? KodeKloud's CI/CD courses cover pipeline design, Docker optimization, and production-grade workflows with hands-on labs.

Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments