Generic Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:


1. Introduction

In a previous article, we explained the difference between Docker images and Docker containers. In short: An image is like a Java class, and containers are like Java objects.

In this tutorial, we'll look at the various ways of removing Docker images.

2. Why Remove Docker Images?

The Docker Engine stores images and runs containers. For that purpose, the Docker Engine reserves a certain amount of disk space as a “storage pool” for images, containers, and everything else (such as global Docker volumes or networks).

Once that storage pool is full, the Docker Engine stops working: We can't create or download new images anymore, and our containers fail to run.

Docker images take up the majority of the Docker Engine storage pool. So we remove Docker images to keep Docker running.

We also remove images to keep our Docker Engine organized and clean. For instance, we can easily create dozens of images during development that we soon don't need anymore. Or, we download some software images for testing that we can dispose of later.

We can easily remove a Docker image that we pulled from a Docker repository: If we ever need it again, we'll just pull it from the repository once more.

But we have to be careful with Docker Images we created ourselves: Once removed, our own images are gone unless we saved them! We can save Docker images by pushing them to a repository or exporting them to a TAR file.

3. Downloading PostgreSQL 13 Beta Images

PostgreSQL is an open-source relational database. We'll use the first two PostgreSQL 13 beta Docker images as examples. These two images are relatively small, so we can download them quickly. And because they are beta software, we don't have them in our Docker Engine already.

We'll use the beta 2 image to create a container. We won't use the beta 1 image directly.

But before we download these two images, let's check first how much space Docker images take up in the storage pool:

docker system df --format 'table {{.Type}}\t{{.TotalCount}}\t{{.Size}}'

Here's the output from a test machine. The first line shows that our 71 Docker images use 7.8 GB:

TYPE                TOTAL               SIZE
Images              71                  7.813GB
Containers          1                   359.1MB
Local Volumes       203                 14.54GB
Build Cache         770                 31.54GB

Now we download the two PostgreSQL images and recheck the Docker storage pool:

docker pull postgres:13-beta1-alpine
docker pull postgres:13-beta2-alpine
docker system df --format 'table {{.Type}}\t{{.TotalCount}}\t{{.Size}}'

As expected, the number of images increased from 71 to 73. And the overall image size went from 7.8 GB to 8.1 GB.

We'll just show the first line for brevity:

TYPE                TOTAL               SIZE
Images              73                  8.119GB

4. Removing a Single Image

Let's start a container with the PostgreSQL 13 beta 2 image. We set secr3t as the password for the database root user because the PostgreSQL container won't start without one:

docker run -d -e POSTGRES_PASSWORD=secr3t postgres:13-beta2-alpine
docker ps --format 'table {{.ID}}\t{{.Image}}\t{{.Status}}'

Here is the running container on the test machine:

CONTAINER ID        IMAGE                      STATUS
527bfd4cfb89        postgres:13-beta2-alpine   Up Less than a second

Now let's remove the PostgreSQL 13 beta 2 image. We use docker image rm to remove a Docker image. That command removes one or more images:

docker image rm postgres:13-beta2-alpine

This command fails because a running container still uses that image:

Error response from daemon: conflict: unable to remove repository reference "postgres:13-beta2-alpine" (must force) - container 527bfd4cfb89 is using its referenced image cac2ee40fa5a

So let's stop that running container by using its ID, which we obtained from docker ps:

docker container stop 527bfd4cfb89

We now try to remove the image again – and get the same error message: We can't remove an image used by a container, running or not.

So let's remove the container. Then we can finally remove the image:

docker container rm 527bfd4cfb89
docker image rm postgres:13-beta2-alpine

The Docker Engine prints details of the image removal:

Untagged: postgres:13-beta2-alpine
Untagged: [email protected]:b3a4ebdb37b892696a7bd7e05763b938345f29a7327fc17049c7148c03ff6a92
removed: sha256:cac2ee40fa5a40f0abe53e0138033fe7a9bcee28e7fb6c9eaac4d3a2076b1a86
removed: sha256:6a14bab707274a8007da33fe08ea56a921f356263d8fd5e599273c7ee4880170
removed: sha256:5e6ef40b9f6f8802452dbca622e498caa460736d890ca20011e7c79de02adf28
removed: sha256:dbd38ed4b347c7f3c81328742a1ddeb1872ad52ac3b1db034e41aa71c0d55a75
removed: sha256:23639f6bd6ab4b786e23d9d7c02a66db6d55035ab3ad8f7ecdb9b1ad6efeec74
removed: sha256:8294c0a7818c9a435b8908a3bcccbc2171c5cefa7f4f378ad23f40e28ad2f843

The docker system df confirms the removal: The number of images decreased from 73 to 72. And the overall image size went from 8.1 GB to 8.0 GB:

TYPE                TOTAL               SIZE
Images              72                  7.966GB

5. Removing Multiple Images by Name

Let's download the PostgreSQL 13 beta 2 image again that we just removed in the previous section:

docker pull postgres:13-beta2-alpine

Now we want to remove both the beta 1 image and the beta 2 image by name. We've only used the beta 2 image so far. As mentioned earlier, we're not using the beta 1 image directly, so we can just remove it now.

Unfortunately, docker image rm doesn't offer a filter option for removing by name. Instead, we'll chain Linux commands to remove multiple images by name.

We'll reference images by repository and tag, like in a docker pull command: The repository is postgres, the labels are 13-beta1-alpine and 13-beta2-alpine.

So, to remove multiple images by name, we need to:

  • List all images by repository and tag, such as postgres:13-beta2-alpine
  • Then, filter those output lines through a regular expression with the grep command: ^postgres:13-beta
  • And finally, feed those lines to the docker image rm command

Let's start putting these together. To test for correctness, let's run just the first two of these pieces:

docker image ls --format '{{.Repository}}:{{.Tag}}' | grep '^postgres:13-beta'

And on our test machine we get:


Now given that, we can add it to our docker image rm command:

docker image rm $(docker image ls --format '{{.Repository}}:{{.Tag}}' | grep '^postgres:13-beta')

As before, we can only remove images if no container, running or stopped, uses them. We then see the same image removal details as in the previous section. And docker system df shows that we're back to 71 images at 7.8 GB on the test machine:

TYPE                TOTAL               SIZE
Images              71                  7.813GB

This image removal command works in a terminal on Linux and Mac. On Windows, it requires the “Docker Quickstart Terminal” of the Docker Toolbox. In the future, the more recent Docker Desktop for Windows may work with this Linux command on Windows 10, too.

6. Removing Images by Size

An excellent way to save disk space is to remove the largest Docker images first.

Now docker image ls can't sort by size, either. So, we list all images and sort that output with the sort command to view images by size:

docker image ls | sort -k7 -h -r

which on our test machine outputs:

collabora/code         8ae6850294e5   3 weeks ago  1.28GB
nextcloud        19.0.1-apache   25b6e2f7e916   6 days ago   752MB
nextcloud        latest          6375cff75f7b   5 weeks ago  750MB
nextcloud        19.0.0-apache   5c44e8445287   7 days ago   750MB

Next, we manually review to find what we want to remove. The ID, column three, is easier to copy and paste than the repository and tag, columns one and two. Docker allows removing multiple images in one go.

Let's say we want to remove nextcloud:latest and nextcloud:19.0.0-apache. Simply put, we can look at their corresponding IDs in our table and list them in our docker image rm command:

docker image rm 6375cff75f7b 5c44e8445287

As before, we can only remove images not used by any container and see the usual image removal details. Now we're down to 69 images at 7.1 GB on our test machine:

TYPE                TOTAL               SIZE
Images              69                  7.128GB

7. Removing Images by Creation Date

Docker can remove images by their creation date. We'll use the new docker image prune command for that. Unlike docker image rm, it is designed to remove multiple images or even all images.

Now, let's remove all images created before July 7, 2020:

docker image prune -a --force --filter "until=2020-07-07T00:00:00"

We still can only remove images not used by any container, and we still see the usual image removal details. This command removed two images on the test machine, so we're at 67 images and 5.7 GB on the test machine:

TYPE                TOTAL               SIZE
Images              67                  5.686GB

Another way to remove images by their creation date is to specify a time span instead of a cut-off date. Let's say we wanted to remove all images older than a week:

docker image prune -a --force --filter "until=168h"

Note that the Docker filter option requires us to convert that time span into hours.

8. Pruning Containers and Images

docker image prune bulk-removes unused images. It goes hand-in-hand with docker container prune, which bulk-removes stopped containers. Let's start with that last command:

docker container prune

This prints a warning message. We have to enter y and press Enter to proceed:

WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
removed Containers:

Total reclaimed space: 359.1MB

So on the test machine, this removed one stopped container.

Now we need need to discuss image relationships briefly. Our Docker images extend other images to gain their functionality, just as Java classes extend other Java classes.

Let's look at the top of the Dockerfile for the PostgreSQL beta 2 image to see what image it's extending:

FROM alpine:3.12

So the beta 2 image uses alpine:3.12. That's why Docker implicitly downloaded alpine:3.12 when we pulled the beta 2 image at first. We don't see these implicitly downloaded images with docker image ls.

Now let's say we removed the PostgreSQL 13 beta 2 image. If no other Docker image extended alpine:3.12, then Docker would consider alpine:3.12 a so-called “dangling image”: A once implicitly downloaded image that's now not needed anymore. docker image prune removes these dangling images:

docker image prune

This command also requires us to enter y and press Enter to proceed:

WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N] y
Total reclaimed space: 0B

On the test machine, this didn't remove any images.

docker image prune -a removes all images not used by containers. So if we don't have any containers (running or not), then this will remove all Docker images! That is a dangerous command indeed:

docker image prune -a

On the test machine, this removed all images. docker system df confirms that neither containers nor images are left:

TYPE                TOTAL               SIZE
Images              0                   0B
Containers          0                   0B

9. Conclusion

In this article, we first saw how we could remove a single Docker image. Next, we learned how to remove images by name, size, or creation date. Finally, we learned how to remove all unused containers and images.

Generic bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

Comments are closed on this article!