CertGrid CertGrid
Best Practices·Docker

Docker Disk Usage and Cleanup

Where the space actually goes - measured, not guessed. On this host the build cache was the biggest reclaimable item and dangling images were zero, which is the opposite of the usual advice.

Operations and Troubleshooting Guide 32 of 46 Intermediate

Tested on the versions above. Every size here is from one real host. Yours will differ - the point is the method and the ordering, not the numbers.

One Docker host is all this guide needs. Nothing here depends on a second machine, and the hardware above is modest on purpose - a 2 core, 4 GB VM runs everything in this path.
Server NameIP AddressOSRolesCPURAMHDD
DOCKER01192.168.0.21Ubuntu 26.04 LTSDocker Host2 Core4 GB50 GB

Before you start

  1. Account before you delete

    docker system df breaks usage into four categories and tells you how much of each is reclaimable. Run this first, always. Deleting before measuring is how people remove an image they needed and keep the 400 MB of build cache they did not.

    bash Example session
    docker system dfTYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLEImages          9         0         819.7MB   806.7MB (98%)Containers      0         0         0B        0BLocal Volumes   1         0         0B        0BBuild Cache     45        0         306.6MB   254.3MB

    Expected resultFour rows with a reclaimable column.

    Success conditionYou can name the biggest reclaimable category before touching anything. Note ACTIVE 0 - nothing was running, so everything here is idle, not in use.

  2. Watch a build move the numbers

    Building one image with a 40 MB layer, then rebuilding it with slightly different content, changes two categories at once. Images grew as expected - but look at the build cache, which grew by more than the image did.

    bash Example session
    docker system dfTYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLEImages          10        0         905.7MB   880MB (97%)Build Cache     49        0         476.5MB   338.2MB

    Expected resultImages up by roughly one image, and build cache up by more.

    Success conditionYou have seen the build cache outgrow the artefact it produced. On a CI worker this is usually the number one consumer.

  3. The dangling-image assumption is often wrong

    The standard advice is that rebuilding leaves images behind that you should prune. On this host, with BuildKit, rebuilding and retagging produced NO dangling images at all - the filter returns nothing and the prune reclaims zero bytes. Check before you believe the folklore, on your own host and your own builder.

    bash
    docker images --filter dangling=true --filter label=cg-demo=1 --format "{{.ID}} {{.Size}}"# no output - no dangling images were createddocker image prune -f --filter label=cg-demo=1Total reclaimed space: 0B

    Expected resultNothing dangling, and zero bytes reclaimed.

    Success conditionYou measured instead of assuming. Where the space went was the build cache, which docker image prune does not touch at all.

  4. Container size does not include volume data

    docker ps --size reports the writable layer and the virtual size. A container that wrote 20 MB into a mounted volume still shows a tiny writable layer, because volume data is not part of the container. Volumes are counted separately in system df, and they are the category most often overlooked.

    bash Example session
    docker ps --size --filter name=cg-disk-c --format "table {{.Names}}\t{{.Size}}"NAMES       SIZEcg-disk-c   8.19kB (virtual 8.99MB)

    Expected resultA writable layer of a few kilobytes despite the container writing 20 MB.

    Success conditionYou know why a "small" container can still be responsible for gigabytes. Use docker system df -v to attribute volume space.

  5. Delete by name first, prune second

    Named removal is precise and reversible in the sense that you know exactly what went. Prune is a blunt instrument that removes everything matching a category, including things another person on the host still needs. Prefer names, and when you must prune, scope it with a filter.

    bash
    docker rm -f cg-disk-c && docker volume rm cg-disk-v && docker rmi cg-disk:1cg-disk-ccg-disk-vUntagged: cg-disk:1Deleted: sha256:0bfa7cbe837fc6bd5fe6399029a4580b84248d35130bc778913d050739e6362b

    Expected resultImages back to the count and size you started with.

    Verify it worked

    bash Example session
    docker system dfTYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLEImages          9         0         819.7MB   806.7MB (98%)

    Success conditionThe accounting returned to baseline. That is what a clean-up should look like - a number you can check, not a hope.

  6. The prune commands, in order of danger

    Know what each one takes before you run it. docker container prune removes stopped containers. docker image prune removes dangling images; with -a it removes every image not used by a container, which on a shared machine will delete someone else's work. docker volume prune removes unused volumes and that is DATA. docker builder prune removes build cache and is usually the one that actually frees the space.

    bash
    # all of these are irreversible - none of them prompt for anything except a yes/nodocker container prune          # stopped containersdocker builder prune            # build cache - usually the big windocker image prune -a           # EVERY unused image, not just danglingdocker volume prune             # DELETES DATAdocker system df# run this first, and again after, so you can prove what you reclaimed

    Expected resultYou reading the list rather than running them all.

    Success conditionYou can say which command would have freed the most on the host above - docker builder prune, at 424 MB reclaimable, not any image command.

  7. Scope a prune with a filter

    Filters turn a blunt command into a precise one. label= is the strongest: label everything a build or a CI job creates, then prune only that label. until= is the common time-based scope. On a machine you share, an unfiltered prune is not yours to run.

    bash
    docker image prune -f --filter label=cg-demo=1Total reclaimed space: 0B# only images carrying that label were considereddocker builder prune --filter until=168h    # cache older than a week

    Expected resultA prune that reports what it reclaimed, having considered only the labelled subset.

    Success conditionYou have a cleanup you could safely put in a cron job. An unfiltered one you could not.

Troubleshooting

Official sources