← Back to Blog
DevOps September 26, 2026

Stop your CI/CD runners from bloating with dangling Docker images

You’re mid-deploy when the build fails with no space left on device. You check df -h and see the disk is full, but du -sh /var/lib/docker shows it’s only half-full. The culprit isn’t the containers; it’s the accumulation of dangling images and build cache layers from hundreds of failed or successful builds over the past few weeks. Your CI runner is effectively holding onto gigabytes of obsolete artifacts, causing slow builds and eventual disk exhaustion.

The 5-minute setup

First, quantify the waste. Run this to see exactly how much space is being consumed by unused resources:

docker system df

You’ll likely see significant usage in Images (specifically dangling ones) and Build Cache. To safely remove only the truly unused data without touching running containers or tagged images, use the prune command with specific filters. This is safer than docker system prune -a, which can delete tagged images you actually need.

# Remove all dangling images (untagged, e.g., <none>)
docker image prune -f

# Remove build cache older than 24 hours
docker builder prune -f --filter "until=24h"

If you want to automate this to prevent recurrence, add a simple cron job or a step in your CI pipeline that runs daily. Here’s a robust one-liner for a cron entry that logs the output for auditability:

0 3 * * * /usr/bin/docker image prune -f >> /var/log/docker-prune.log 2>&1 && /usr/bin/docker builder prune -f --filter "until=24h" >> /var/log/docker-prune.log 2>&1

For more granular control in a CI environment, you can also target specific repositories. If you know your CI runner only handles images prefixed with myapp/, you can use docker image prune with a filter to ensure you never accidentally delete a base image or a different project’s artifact:

docker image prune -f --filter "label=keep"

Note: Tag your critical base images with --label keep=true during the build stage if you use this filter approach.

Why it works

Docker’s storage driver (Overlay2) keeps layers on disk until explicitly removed. When you build an image multiple times, old layers and intermediate states remain as "dangling" images or build cache entries. docker image prune targets images with no tags (the <none> tag), which are by definition unreferenced by any container or tag. docker builder prune removes the BuildKit cache, which stores intermediate layers used during multi-stage builds. By filtering by time (until=24h) or label, you ensure that only truly obsolete data is removed, preserving the integrity of active deployments and base images.

Pro Tip

Check your Docker daemon configuration for max-concurrent-downloads and max-concurrent-uploads in /etc/docker/daemon.json. If your CI runner is pulling large images frequently, increasing these values (e.g., to 3-5) can significantly speed up build times. More importantly, verify that log-driver is set to json-file with max-size and max-file limits to prevent container logs from filling up the disk alongside images:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

After changing daemon.json, restart Docker (systemctl restart docker) to apply the changes. This combination of image pruning and log rotation prevents the most common causes of disk exhaustion on CI/CD runners.