Cleaning Up Docker Disk Usage

Docker can use a surprising amount of disk space over time. Images, stopped containers, unused networks, named volumes, and BuildKit cache all live outside your project folder, so deleting source code does not necessarily free Docker storage. Cleaning it up safely means knowing what Docker considers unused and when a prune command can delete data you still care about.

Overview: How Docker Disk Usage Works

Docker stores several different kinds of objects. Images are read-only layer stacks. A container is an image plus a thin writable layer and runtime metadata. Volumes are Docker-managed storage objects that survive container removal. Networks connect containers. Build cache stores intermediate build results so future docker build commands can reuse layers instead of rebuilding everything.

These objects share storage below Docker’s data root. On Linux Engine this is commonly under /var/lib/docker, often using the overlay2 storage driver for image and container layers. On Docker Desktop for macOS and Windows, Linux container storage lives inside Docker Desktop’s virtual machine disk. In both cases, the safe interface is the Docker CLI, not deleting files from Docker’s internal directories by hand.

Disk usage grows naturally during normal development. Every pull may add image layers. Every build may add cache records. Every failed experiment may leave stopped containers. A Compose project may leave named volumes by design, because database and upload data should outlive container recreation. CI runners also build many image variants and can fill disks quickly if they never prune.

The key distinction is unused versus important. Docker can tell whether an image, container, network, volume, or build cache record is currently referenced. Docker cannot tell whether an unused volume contains the only copy of your development database. That is why docker system prune is useful, but docker system prune --volumes deserves extra care.

Use docker system df before cleanup. It summarizes how much space is used by images, containers, local volumes, and build cache, and how much Docker believes is reclaimable. Use docker system df --verbose when you need names and per-object detail. Then choose the narrowest prune command that matches the problem: container prune for stopped containers, image prune for unused images, builder prune for build cache, volume prune only for intentionally disposable volumes, and system prune for a broader sweep.

Syntax

docker system df [--verbose]
docker system prune [--all] [--volumes] [--filter "until=24h"] [--force]
docker builder prune [--all] [--filter "until=24h"] [--force]
Command or option Meaning
docker system df Shows Docker disk usage by object type.
--verbose Shows more detail, including images, containers, volumes, and cache records.
docker system prune Removes stopped containers, unused networks, dangling images, and build cache.
--all Also removes unused images, not just dangling images. Images used by containers are kept.
--volumes Also removes unused local volumes. This can delete persistent data.
--filter "until=24h" Only prunes objects older than the given duration or timestamp where supported.
--force Skips the interactive confirmation prompt. Useful in scripts and CI.
docker builder prune Cleans BuildKit build cache without pruning containers, networks, or volumes.

Without --all, docker system prune removes dangling images, which are untagged image layers no longer referenced by a named image. With --all, it removes any image not used by at least one container. That can be good on a CI runner, but annoying on a laptop because the next docker run may need to pull images again.

Examples

Example 1: Inspect Docker disk usage before deleting anything

docker system df
docker system df --verbose

Output:

TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          18        6         8.41GB    4.92GB (58%)
Containers      12        3         623MB     411MB (65%)
Local Volumes   7         4         2.13GB    580MB (27%)
Build Cache     42        0         6.88GB    6.88GB

This is the safest first command because it does not delete anything. ACTIVE means an object is currently referenced, such as an image used by a container or a volume mounted by a container. RECLAIMABLE is Docker’s estimate of bytes that could be removed because they are not currently referenced.

Example 2: Clean stopped containers, dangling images, and old build cache separately

docker container prune --force
docker image prune --force
docker builder prune --force --filter "until=24h"

Output:

Deleted Containers:
7f9a1a1f5b8c
b4c2d901772e

Deleted Images:
deleted: sha256:6a7f0b6a1d2a9c2d4e6b8f00112233445566778899aabbccddeeff0011223344

Total reclaimed space: 3.2GB

This pattern is good for developer machines because each command targets one category. It removes stopped containers, untagged dangling images, and build cache older than 24 hours. It does not remove named volumes, so a stopped database’s volume is not silently deleted.

Example 3: Use a broader system prune on a disposable CI runner

docker system prune --all --force --filter "until=168h"

Output:

Deleted Containers:
1df4b8b2a991

Deleted Networks:
feature-tests_default

Deleted Images:
untagged: registry.example.com/web-api:pr-184
untagged: registry.example.com/web-api:pr-185

Deleted build cache objects:
q3c2n9u1f7e4
z8x6k4m2p0r1

Total reclaimed space: 11.6GB

This command is reasonable for CI workers where local Docker state is disposable and images can be rebuilt or pulled again. The filter keeps objects newer than seven days. It does not include --volumes, because even in CI, test databases or cache volumes may be intentionally reused between jobs unless the runner is designed to be fully ephemeral.

Example 4: Prune only volumes that were labeled as disposable

docker volume create --label cleanup=true temp-test-data
docker volume ls --filter label=cleanup=true
docker volume prune --force --filter "label=cleanup=true"

Output:

temp-test-data
DRIVER    VOLUME NAME
local     temp-test-data
Deleted Volumes:
temp-test-data

Total reclaimed space: 16.4MB

Volume cleanup should be explicit. This example creates a volume with a label, lists matching volumes, and prunes only unused volumes carrying that label. Docker will not remove a volume still used by a container, but it can remove an unused volume that contains important data, so labels and backups matter.

How It Works Step By Step

  1. The Docker CLI sends the df or prune request to the Docker daemon.
  2. For docker system df, the daemon walks its metadata store and estimates size by object type. Shared image layers are stored once, so the numbers are not the same as adding every image’s displayed size.
  3. For prune commands, Docker finds objects that are not currently referenced. A stopped container is removable. An image used by any container is kept. A network used by a container is kept.
  4. When --filter is provided, Docker narrows the candidate list by age or label where that prune command supports the filter.
  5. Without --force, Docker prints a warning and asks for confirmation. With --force, it proceeds immediately.
  6. Docker deletes metadata and filesystem content for the selected objects. For image layers and cache records, shared layers remain until no remaining image or cache entry references them.
  7. On Docker Desktop, freed Linux-container storage may not always shrink the host VM disk file immediately, but it becomes reusable by Docker inside the VM.

Common Mistakes

Running the most destructive prune first

docker system prune --all --volumes --force

This is valid syntax, but it is a risky first move on a workstation. It can remove unused images, stopped containers, unused networks, build cache, and unused local volumes. The safer fix is to inspect first and prune the specific category that is actually large:

docker system df --verbose
docker builder prune --force --filter "until=24h"

Assuming unused volumes are unimportant

A volume becomes unused when no container currently references it. That does not mean the data is junk. If you removed a PostgreSQL container during an upgrade, its named volume may be unused until the replacement container is created. Inspect and back up important volumes before using docker volume prune or docker system prune --volumes.

Deleting Docker’s internal files by hand

Removing files directly from /var/lib/docker or Docker Desktop’s VM storage can corrupt Docker’s metadata. Use Docker commands such as docker system prune, docker builder prune, and docker volume rm. If Docker’s metadata is already inconsistent, stop and repair Docker rather than manually deleting random layer directories.

Expecting prune to fix oversized images

Prune removes unused local objects. It does not make a tagged production image smaller. If an image is too large, fix the Dockerfile with .dockerignore, cache-aware copy order, production dependencies, and multi-stage builds. Remember that deleting a file in a later layer does not remove bytes stored in an earlier image layer.

Best Practices

  • Run docker system df before cleanup so you know which category is consuming space.
  • Prefer narrow commands such as docker builder prune or docker container prune when only one category is the problem.
  • Avoid --volumes unless volumes are disposable, backed up, or selected with labels.
  • Use --filter "until=..." on shared machines and CI runners to avoid removing very recent build state.
  • Label temporary volumes and other disposable resources so cleanup can be filtered.
  • Do not manually edit Docker’s storage directory.
  • In CI, run cleanup after jobs on disposable runners, but document whether caches and volumes are meant to persist.
  • Use pinned image tags in builds so cleanup and later rebuilds are reproducible; avoid depending on latest.
  • Keep images small at build time with .dockerignore and multi-stage builds instead of relying on prune later.
  • Keep secrets out of image layers and build cache. A later RUN rm does not erase secrets from earlier layers.

Practice Exercises

  1. Run docker system df and identify whether images, containers, volumes, or build cache are using the most reclaimable space. Hint: do not delete anything yet.
  2. Write a cleanup sequence for a development machine that removes stopped containers, dangling images, and build cache older than 48 hours without removing volumes.
  3. Create two test volumes, label only one with cleanup=true, and design a command that prunes only unused volumes with that label. Expected end state: the unlabeled volume remains.

Summary

  • Docker disk usage comes from images, containers, volumes, networks, and build cache.
  • docker system df is the safest starting point because it shows usage without deleting data.
  • docker system prune removes broad categories of unused objects; --all expands image cleanup.
  • --volumes can delete persistent data, so use it only with clear intent.
  • docker builder prune is often the best fix when BuildKit cache fills a disk.
  • Filters and labels make cleanup safer in CI and on shared Docker hosts.
  • Prune frees local disk space, but it does not replace good Dockerfile design for small, reproducible images.