1. Introduction

We’ve already seen 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 focus on 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 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 don’t need anymore. In other cases, 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.

However, we have to be careful with Docker Images we created ourselves. Once removed, our own images are gone unless we saved or uploaded them elsewhere. 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. Because they’re 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.

Before we download these two images, let’s first check 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

Thus, we can see how quickly image storage grows.

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}}'

Let’s see the running container on the test machine:

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

Now, we remove the PostgreSQL 13 beta 2 image. In particular, we use docker image rm to remove a Docker image. This command removes one or more images:

$ docker image rm postgres:13-beta2-alpine

The 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

At this point, we try to remove the image again and get the same error message. We can’t remove an image used by a container, whether it’s 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: postgres@sha256: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

This way, we freed up some space.

5. Removing Multiple Images by Name

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

$ docker pull postgres:13-beta2-alpine

Now, we want to remove both the beta 1 and the beta 2 images by name. So far, we’ve only used the beta 2 image. 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 chain Linux commands to remove multiple images by name.

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

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

  1. list all images by repository and tag, such as postgres:13-beta2-alpine
  2. filter the list through a regular expression with the grep command: ^postgres:13-beta
  3. feed leftover lines to the docker image rm command

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

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

This should yield the resulting images:


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. Then, we 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 storage space is to remove the largest Docker images first.

However, 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

Let’s see a sample output:

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 often easier to copy and paste than the repository and tag, which are columns one and two, respectively. Docker enables us to remove multiple images in one go by specifying each identifier.

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 we 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

So far, we managed to free up considerable space by removing unwanted images.

7. Removing Images by Creation Date

Docker can also remove images by their creation date. We use the docker image prune command for this. Unlike docker image rm, it’s designed to remove multiple or even all images.

For example, let’s remove all images created before July 7, 2020:

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

We can still only remove images not used by any container, and we still see the usual image removal details.

The above 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 want to remove all images older than a week:

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

Notably, the Docker filter option requires converting 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 the last command:

$ docker container prune

This command 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, the command above removed one stopped container.

Now, let’s briefly discuss image relationships. Docker images extend other images to gain their functionality, just like 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. Importantly, 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”, which is a once implicitly downloaded image that’s not needed anymore. docker image prune removes dangling images:

$ docker image prune

This command also requires us to input 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 the command removes all Docker images. This 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

Thus, using prune should be used with caution, especially with the -a flag.

9. Forcefully Remove Containers and Images

The docker prune command removes the stopped containers and dangling images.

9.1. Remove All Containers

Although it’s relatively dangerous, we can request a deletion of all containers, even if they are running:

$ docker rm $(docker ps -qa)

Here, the –quiet (-q) flag reduces the output to only the container identifiers, while –all (-a) ensures we get all containers, not just the running ones. So, the rm command acts on all existing containers, regardless of their state.

However, this command may error out on containers that are running:

Error response from daemon: cannot remove container "/<CONTAINER_NAME>":
container is running: stop the container before removing or force remove

Effectively, this prevents mistyped rm commands from wiping all containers. Further, if we only want non-running containers deleted, using this approach is helpful without specifying a complex query, as long as the containers we don’t want removed are running.

9.2. Force-Remove All Containers

On the other hand, we can force Docker to delete all containers, regardless of their state:

$ docker rm -f $(docker ps -qa)

This command stops any running containers via SIGKILL and removes all containers. The -f flag is used to remove the running Docker containers forcefully.

Notably, the –force (-f) flag is only necessary and desired when we don’t mind all containers losing their current state. Since we are deleting them anyway, that certainly should be the case.

9.3. Remove All Images

As we already saw, a container image can remain after removing the container and we can use prune to delete that. Yet, if we don’t remove a container, trying to delete its image results in an error:

$ docker rmi $(docker images -aq)
Error response from daemon: conflict: unable to remove repository reference "<IMAGE_NAME>"
(must force) - container <CONTAINER_ID> is using its referenced image <IMAGE_ID>

The docker images -qa command returns the image ID of all the Docker images.

So, let’s follow the suggestion and again use the –force flag.

9.4. Force-Remove All Images

To force-delete all images, we use the –force (-f) flag:

$ docker rmi -f $(docker images -aq)

Again, the -f flag is used to forcefully remove the Docker image.

However, if a running container uses the image, even the –force flag can’t delete it and only results in an untagged image:

Untagged: <IMAGE_NAME>@sha256:<IMAGE_ID>

This happens for each image that’s currently in use. Let’s see what an untagged image looks like:

$ docker image ls
<none>       <none>    3db8720ecbf5   11 days ago   66.9MB

So, as long as we don’t have other such images, we’re at least aware that there was an attempt to remove the image. Further, prune should be able to remove the image after the container stops.

10. Conclusion

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

Inline Feedbacks
View all comments
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.