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
- The Docker CLI sends the
dforprunerequest to the Docker daemon. - 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. - 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.
- When
--filteris provided, Docker narrows the candidate list by age or label where that prune command supports the filter. - Without
--force, Docker prints a warning and asks for confirmation. With--force, it proceeds immediately. - 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.
- 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 dfbefore cleanup so you know which category is consuming space. - Prefer narrow commands such as
docker builder pruneordocker container prunewhen only one category is the problem. - Avoid
--volumesunless 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
.dockerignoreand multi-stage builds instead of relying on prune later. - Keep secrets out of image layers and build cache. A later
RUN rmdoes not erase secrets from earlier layers.
Practice Exercises
- Run
docker system dfand identify whether images, containers, volumes, or build cache are using the most reclaimable space. Hint: do not delete anything yet. - Write a cleanup sequence for a development machine that removes stopped containers, dangling images, and build cache older than 48 hours without removing volumes.
- 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 dfis the safest starting point because it shows usage without deleting data.docker system pruneremoves broad categories of unused objects;--allexpands image cleanup.--volumescan delete persistent data, so use it only with clear intent.docker builder pruneis 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.
