Tao
Tao

Docker Prune: From Beginner to Expert

While Docker is incredibly useful, it comes with hidden costs: over time, images, containers, volumes, and networks pile up and quietly consume large amounts of disk space. This isn’t a design flaw in Docker, but rather the result of its core design philosophy. Docker is naturally “conservative” when managing resources—it won’t delete anything on its own, leaving the cleanup work to you. If you don’t actively clean up, your server’s hard drive will quickly fill up.

Understanding what consumes the most space is the first step to effective cleanup. Here are the five main sources of “garbage”:

  1. Stopped Containers:
    Every time you run a container (even just a one-time command), Docker creates a container instance. These containers don’t automatically disappear after stopping—they still keep their filesystem layers and take up space.

  2. Dangling Images:
    This is the most common source of wasted space. When you rebuild an existing image (for example, docker build -t my-app .), the old version isn’t deleted. Instead, it loses its tag and becomes a “dangling” or “orphaned” image, showing up as <none>:<none> in listings. These images are no longer used by any container and are purely historical leftovers.

  3. Unused Images:
    This covers a broader range than dangling images. An image might have a proper tag (like ubuntu:20.04), but if no containers (running or stopped) are using it, it’s an unused image. You might have downloaded many base images or test images and forgotten to delete them after use.

  4. Unused Volumes:
    Volumes are used for persistent data storage. But if you create a container that uses a volume and later delete the container, that volume won’t be automatically removed. Anonymous volumes (unnamed volumes) are especially easy to forget, growing like digital weeds.

  5. Unused Networks:
    Similar to volumes, when you create custom networks for specific applications but later delete all containers using those networks, the networks themselves don’t disappear. While individual networks don’t take up much space, they accumulate over time.

Regular cleanup of these unused resources not only frees up significant disk space but also makes Docker management more convenient, avoiding the hassle of searching through clutter like “rummaging through boxes to find something.”

To solve these problems, Docker provides a powerful set of prune commands. These commands are like a Swiss Army knife—each tool has a specific purpose.

This is the most powerful “spring cleaning” tool and the most commonly used. It removes all the following unused resources in one go:

  • All stopped containers
  • All dangling images
  • All unused networks
  • All build cache

By default, docker system prune does not remove unused volumes—this is a very important safety feature to prevent you from accidentally deleting important data.

bash

# Perform a comprehensive system cleanup
docker system prune

# Add -a or --all parameter for more aggressive cleanup, removing all unused images (not just dangling ones)
docker system prune -a

# Add --volumes parameter to also remove unused volumes
docker system prune --volumes

Warning: docker system prune --volumes is a dangerous operation. It will delete all volumes not referenced by any container, including named volumes that might contain important data you want to keep. Think twice before using this.

If you don’t want to use the “spring cleaning” command and only want to clean specific types of resources, you can use these more targeted commands. The benefit is clear focus and reduced risk of accidental deletion.

Only removes all stopped containers.

bash

docker container prune

By default, only removes dangling images (<none>:<none>).

bash

# Only remove dangling images
docker image prune

# Use -a or --all parameter to remove all images not used by any container (including tagged ones)
docker image prune -a

Only removes unused volumes.

bash

docker volume prune

Warning Again: This command is very dangerous—once deleted, data is gone forever. Docker’s design philosophy is “data first,” so it won’t touch your volumes by default. Before running this command, make sure you really know what you’re doing and have backed up important data.

Only removes all unused networks.

bash

docker network prune

Docker resources have dependencies: containers depend on images, and volumes and networks are used by containers. Therefore, a safe cleanup order should be:

  1. Stop and remove containers (docker stop / docker rm or docker container prune).
  2. Remove unneeded images (docker image prune).
  3. Remove unneeded volumes (docker volume prune).
  4. Remove unneeded networks (docker network prune).

docker system prune basically helps you execute this sequence automatically.

Just knowing the basic commands isn’t enough. To become a Docker cleanup expert, you must master the parameters that give you precise control.

--all (-a) and --volumes are the most powerful and most easily misunderstood parameters in docker prune commands.

  • --all (-a):
    When used with docker image prune, this parameter expands the cleanup scope from “dangling images” to “all unused images.” This means even if an image has a name (like nginx:latest), as long as no container is using it, it will be deleted. This is very useful for cleaning up images you downloaded for testing but never used again.

  • --volumes:
    This parameter is exclusive to docker system prune. It acts like a switch—once turned on, system prune will also remove unused volumes during cleanup. Again, this can lead to data loss.

  • --force (-f):
    This parameter skips the “Are you sure?” confirmation step and executes deletion directly. It’s useful in automation scripts, but be extra careful when using it manually since you won’t get a chance to reconsider.

The --filter parameter transforms docker prune commands from a “sledgehammer” into a “scalpel.” You can use it to set very specific rules, only deleting resources that meet certain conditions.

The most commonly used filters are until and label.

You can delete resources created before a certain time point. The until value can be a timestamp or a time period (like 24h).

bash

# Remove all containers stopped for more than 24 hours
docker container prune --filter "until=24h"

# Remove all images unused for more than 10 days
docker image prune -a --filter "until=240h"

# Remove all build cache unused for more than 7 days
docker builder prune --filter "until=168h"

This is the most powerful protection mechanism. You can add a special “protection” label to resources you don’t want accidentally deleted, then tell Docker “don’t touch anything with this label” during cleanup.

This “exclusion” approach is a huge shift. It changes you from “delete what you see” mode to “only delete what’s not labeled” mode, greatly improving safety.

bash

# 1. Create a volume with a protection label
docker volume create --label keep=true important_data

# 2. Delete all unused volumes without the "keep=true" label
#    Note the "label!=key=value" syntax
docker volume prune --filter "label!=keep=true"

# 3. Protecting images works the same way
docker tag my-image my-image:latest
# Note: Docker doesn't have a direct command to label images; this is usually done during build with LABEL instruction
docker image prune -a --filter "label!=keep=true"

Once you master filters, you can confidently use docker prune -f in automation scripts because you know it will only delete what you explicitly allow to be deleted.

Knowing how to use commands is one thing; knowing when to use them and how to use them safely is another.

Before executing any prune command, especially with --force, please go through this checklist:

  1. Get a general overview:

    • docker ps -a: See what containers you have and their status.
    • docker images -a: See what images you have and which are dangling.
    • docker volume ls: See what volumes you have, especially anonymous volumes with strange names.
    • docker network ls: See what networks you have.
  2. Manual inspection:
    For resources you’re unsure about, like a volume that looks like it contains important data, use docker volume inspect <volume_name> to check its details.

  3. Rehearse before executing:
    Run the command without --force to see what Docker says it will delete. After confirming it’s correct, use --force if needed.

  4. Backup! Backup! Backup!
    For production environments or any volumes containing important data, always backup before deletion. Once deleted, it’s really gone forever.

In development environments, you can boldly use docker system prune -a --volumes to free up space. But in production environments, this is like playing with fire. The core principle in production is “stability above all.”

Warning: Don’t randomly run docker system prune on production servers, especially when you don’t fully understand what applications and data are running on the server.

Production environment cleanup strategy should be:

  • Better to delete one by one than everything at once: Use more precise commands like container prune, image prune with --filter.
  • Be extra careful with automation: If you must automate cleanup in production, always use filters with “protection” labels to ensure core applications and data are never accidentally deleted.
  • Understand applications, don’t just rely on tools: docker prune is a single-minded tool—it only looks at whether resources “are referenced” without caring about their business importance. Before cleanup, you must understand the role of each volume and image from an application perspective.

CI/CD environments are disaster zones for Docker garbage generation. Every build and test produces large amounts of temporary images and containers. Without control, CI/CD server disks will quickly explode.

A typical CI/CD process might include:

  1. Pull the latest code.
  2. Build a new image using Dockerfile.
  3. Start a container with this new image to run tests.
  4. After tests pass, push the image to a repository.

This process leaves behind at least one dangling image (old version) and one stopped container. Over time, the numbers become staggering. Therefore, adding cleanup steps at the end of CI/CD pipelines is crucial.

In GitLab CI/CD, you can add cleanup commands in the after_script section of your .gitlab-ci.yml file. Commands in after_script run whether the pipeline succeeds or fails, making them perfect for cleanup work.

yaml

stages:
  - build
  - test
  - cleanup

build_job:
  stage: build
  script:
    - docker build -t my-app:$CI_COMMIT_SHA .
    - docker push my-app:$CI_COMMIT_SHA

test_job:
  stage: test
  script:
    - docker run my-app:$CI_COMMIT_SHA npm test

cleanup_job:
  stage: cleanup
  script:
    # Only clean resources older than 1 hour, giving parallel tasks enough time
    - docker system prune -a -f --filter "until=1h"
    # For safer approach, you can just clean dangling images and stopped containers
    # - docker image prune -f
    # - docker container prune -f
  # Ensure this task always runs
  when: always

Note: Using the until filter is a good practice to avoid cleaning resources that might still be used by other parallel tasks.

docker system prune clears build cache, meaning the next build will start from scratch and be slower. This is a typical trade-off between “performance” and “cleanliness.”

  • For performance: Keep build cache for faster builds. But disk usage will continue growing.
  • For cleanliness: Clean build cache every time (docker builder prune). Low disk usage, but every build is slow like the first time.

The best compromise is periodic cleanup rather than cleaning every time. For example, you can set up a weekly scheduled task specifically for cleaning old build cache.

bash

# Only delete build cache unused for more than a week
docker builder prune --filter "until=168h"

docker-compose users often struggle with: Should I use docker-compose down or docker system prune? They’re very different.

Feature docker-compose down docker system prune
Scope Project scope: Only manages resources defined and created by the current docker-compose.yml file. System scope: Manages all Docker resources on the host, regardless of how they were created.
Default behavior - Remove containers
- Remove networks
- Remove stopped containers
- Remove dangling images
- Remove unused networks
Volume handling Doesn’t remove volumes by default. Need -v or --volumes parameter. Doesn’t remove volumes by default. Need --volumes parameter.
Image handling Doesn’t remove images by default. Need --rmi all (remove all) or --rmi local (only remove untagged) parameter. Only removes dangling images by default. Add -a to remove all unused.

A typical disaster scenario:
You have an important application (like a database) started with docker-compose that uses a named volume my_db_data. Later, to clean up disk space, you run docker system prune --volumes in another project directory or root directory. Since the docker-compose containers might be stopped at that time, the prune command will consider my_db_data an “unused” volume and ruthlessly delete it.

Think of it this way:

  • docker-compose down is for dismantling a specific application.
  • docker system prune is for doing spring cleaning on the entire system.

They serve completely different purposes and should never be mixed up.

This is the most common question: “I ran prune, but df -h shows no change in disk space!”

Most common reasons:

  • Docker daemon still holding file handles: Sometimes, even after resources are deleted, the Docker daemon might not have released file locks. Restarting the Docker daemon usually solves this (systemctl restart docker).
  • The real space-consuming stuff wasn’t deleted: prune doesn’t delete volumes and tagged images by default. Maybe you really need docker system prune -a --volumes.
  • “Hidden” occupied space: The real space consumers are in Docker’s overlay2 directory (usually at /var/lib/docker/overlay2). Even after deleting containers and images, some underlying storage layers might not be properly cleaned due to various reasons. Restarting Docker or, in extreme cases, cleaning the entire Docker directory (very dangerous!) might be the last resort.

You’re sure a container is stopped and an image isn’t being used, but prune just won’t delete it.

Possible reasons:

  • Something is still using it: You might have missed a container using that resource (even stopped containers occupy images and volumes). Carefully check with docker ps -a and docker volume inspect.
  • It’s the base for another image: An image might be a base layer (parent image) for another image. In this case, unless you delete all child images depending on it, it won’t be deleted.

When you have thousands of Docker objects, docker system prune might run very slowly or even hang. This is because it needs to figure out all the relationships between everything and calculate what can be safely deleted—a very I/O-intensive operation.

Solutions:

  • Step-by-step cleanup: Don’t use system prune; instead, run container prune, image prune, etc. separately.
  • Use filters: The until filter can significantly reduce the number of objects to check, thus speeding things up.
  • Regular cleanup: Don’t wait until you have tens of thousands of objects before thinking about cleanup. Develop a habit of regular cleanup, handling small amounts each time for much faster operation.

docker prune is an essential tool, but it’s also a double-edged sword. Docker’s naturally “conservative” design means system maintenance responsibility ultimately falls on the user.

  • Default is safe: The default behavior of prune commands is relatively safe—it won’t touch your data (volumes) or useful images.
  • Beware dangerous parameters: --all and --volumes greatly increase cleanup intensity but also bring risk of data loss.
  • Filters are your safety net: Learning to use label and until filters lets you enjoy automation convenience while ensuring core resource safety.
  • Environment determines strategy: Development environments can be aggressive; production environments must be cautious.
  • Cleanup is mandatory work, not optional: Ignoring Docker cleanup will eventually lead to disk exhaustion, performance degradation, and management chaos.

The general rule is: When in doubt, don’t delete. Before deleting, check. For important data, always backup.

Related Content