Baeldung Pro – Ops – NPI EA (cat = Baeldung on Ops)
announcement - icon

Learn through the super-clean Baeldung Pro experience:

>> Membership and Baeldung Pro.

No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.

Partner – Orkes – NPI EA (cat=Kubernetes)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

1. Introduction

Images play an important role in the core functionality of Docker. Sometimes, the complicated history of how Docker handled images makes it difficult to identify the correct base image for a specific Docker image.

In this tutorial, we’ll dive into viewing the base image using Docker Scout, dive, and native Docker commands while exploring the limitations of each method.

2. Viewing the Base Image Using Docker Scout

Determining the base image for a given Docker image can be done easily and reliably using Docker Scout in most cases.

If we use Docker Engine, we may need to install Docker Scout separately. However, it comes pre-installed with Docker Desktop.

Let’s view the base image for postgres:17.2:

$ docker scout quickview postgres:17.2
...
...
  Target             │  postgres:17.2       │    3C    35H    16M    36L     1?
    digest           │  80cbdc6c3301        │                                  
  Base image         │  debian:12-slim      │    0C     0H     0M    23L
                     │                      │

The output shows that the base image used for postgres:17.2 is debian:12-slim.

Yet, there are some limitations when using Docker Scout as it exclusively relies on Docker Hub to determine the base image.

As a result, if we have an image built and stored outside the Docker Hub registry, Docker Scout isn’t able to identify it when we use it as a base for another image.

3. Finding the Base Image Manually

In cases where Docker Scout cannot determine the base image, manual inspection may be necessary to identify the correct base image.

However, manual identification can lead to incorrect base image assumptions. This limitation arises from the fact that when a Docker image is constructed, only the changes made to the underlying image are stored in a layer of the new image.

The metadata about the base image used for an image doesn’t exist in any of the layers, making it harder to identify the correct base image.

Let’s preview the process for finding that base image:

  1. Identify the last layer of the base image using the docker history command
  2. View the digest of that layer using the dive tool
  3. Match the digest with the digest of layers of currently existing images in the Docker deployment

The process we use assumes that the base image is present on the same machine. However, if the base image isn’t present on the same machine, the identification process becomes nearly impossible.

3.1. Using docker history to Identify the Base Image

Again, since most privately built images typically have their base image stored locally, we assume that the base image is present in the system.

Let’s list all layers in postgres:17.2:

$ docker history postgres:17.2
IMAGE          CREATED       CREATED BY                                      SIZE      COMMENT
80cbdc6c3301   8 days ago    CMD ["postgres"]                                0B        buildkit.dockerfile.v0
<missing>      8 days ago    EXPOSE map[5432/tcp:{}]                         0B        buildkit.dockerfile.v0
<missing>      8 days ago    STOPSIGNAL SIGINT                               0B        buildkit.dockerfile.v0
...
...
...
<missing>      8 days ago    RUN /bin/sh -c set -eux;  groupadd -r postgr…   4.32kB    buildkit.dockerfile.v0
<missing>      2 weeks ago   CMD ["bash"]                                    0B        buildkit.dockerfile.v0
<missing>      2 weeks ago   ADD rootfs.tar.xz / # buildkit                  74.8MB    buildkit.dockerfile.v0

We know that image configuration typically ends with an ENTRYPOINT or CMD instruction. Upon inspection, we find that there’s a CMD instruction at the very end of the docker history output.

So, let’s take note of the ADD instruction after the second CMD instruction, as we’re looking for an instruction that changes the contents of the image before the CMD instruction of the base image.

3.2. Identifying the Correct Digest via dive

Next, we use the dive tool to obtain the digest for the instruction discussed in the previous step.

To begin with, we may want to create an alias for the dive tool:

$ alias dive='docker run -ti --rm  -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive'

Now, we can inspect postgres:17.2 using dive which returns the digest for the instruction:

$ dive postgres:17.2
┃ ● Layers ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 
Cmp   Size  Command                                                      
     75 MB  FROM blobs                                                   
    4.3 kB  RUN /bin/sh -c set -eux;     groupadd -r postgres --gid=999; 
                                                                         
│ Layer Details ├─────────────────────────────────────────────────────── 
                                                                         
Tags:   (unavailable)                                                    
Id:     blobs                                                            
Digest: sha256:c3548211b8264f8bfa47a6727043a64f1791b82ac965a284a7ea187e9 

Notably, if we had used a different image, we might’ve needed to put in more effort or go through more iterations to identify the digest of the layer. This depends on how the image was built.

3.3. Matching the Digests

Now that we have the digest, we can simply use a Bash for loop or similar construct to inspect the images and find the one that contains this digest:

$ for image in $(docker images -q)
do
    if [[ $(docker inspect $image | grep "sha256:c3548211b8264f8bfa47a6727043a64f1791b82ac965a284a7ea187e971a95e2") ]]
    then
        docker images | grep $image
    fi
done

In our case, we see two images as the output, postgres:17.2 and debian:12-slim:

postgres                17.2                80cbdc6c3301   8 days ago      435MB
debian                  12-slim             762d928e7cfb   2 weeks ago     74.8MB

Since we’re already inspecting postgres:17.2, we move to debian:12-slim to ensure it only contains the layers we saw using dive:

$ docker inspect debian:12-slim | jq '.[].RootFS'
{
  "Type": "layers",
  "Layers": [
    "sha256:c3548211b8264f8bfa47a6727043a64f1791b82ac965a284a7ea187e971a95e2"
  ]
}

Thus, we verify that debian:12-slim is the base image used for postgres:17.2.

The same method can be used to identify the base image for other images. Critically, when we have multiple images branching from the same image as their base, the identification process can become tedious.

4. Conclusion

In this article, we saw why using Docker Scout is one of the most reliable ways to identify the base image. However, because it only scans Docker Hub, it can’t identify the base image for our privately built images.

So, we also covered inspecting and discovering a base image manually using docker history, dive, and a basic Bash script. While it works in some cases, it’s highly prone to error and isn’t as reliable. This is especially true when the base image doesn’t exist on the same system, as the method becomes unusable.