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.
Last updated: December 7, 2024
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.
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.
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:
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.
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.
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.
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.
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.