What Are Docker Image Layers and How Do They Work?
Docker is a widely used containerization platform that enables developers to run and manage applications inside containers. One of the key components of Docker is the Docker image, which acts as a blueprint for creating containers.
Essentially, a Docker image is a static file that contains everything needed to run an application, including the application code, libraries, dependencies, and the runtime environment. It's like a snapshot of a container that, when executed, creates a Docker container.
A Docker image is composed of multiple layers stacked on top of each other. Each layer represents a specific modification to the file system (inside the container), such as adding a new file or modifying an existing one. Once a layer is created, it becomes immutable, meaning it can't be changed. The layers of a Docker image are stored in the Docker engine's cache, which ensures the efficient creation of Docker images.
In this blog post, we’ll take an in-depth look at Docker image layers, how they work, and how they can help us make efficient use of Docker. Let’s get started!
Prerequisites
To follow along with the examples in this post, you need a code editor. You also need to have Docker Desktop installed and running on your system. If you don’t have it already, you can download it from the link: Get Docker.
Try our Docker Images Lab for free
Building a Docker Image
Before diving into the concept of Docker image layers, let’s first build a Docker image. Open your code editor, create a new directory, and inside this directory, create a file named Dockerfile
. Note that you can choose any name you prefer for the directory, but the file must be named Dockerfile
. Then, copy and paste the following content into the Dockerfile.
FROM alpine
LABEL maintainer=kodekloud
RUN apk update && apk add curl
ENTRYPOINT ["curl"]
CMD ["--version"]
A Dockerfile is a text file that contains a set of instructions for building a Docker image. Our example Dockerfile specifies the following instructions:
FROM alpine
: Sets the base image asalpine
, a lightweight Linux distribution.LABEL maintainer=kodekloud
: Assign metadata to the image with amaintainer
key, which in this case is set tokodekloud
.RUN apk update && apk add curl
: Updates the package list and installs thecurl
command-line tool using theapk
package manager.ENTRYPOINT ["curl"]
: Sets the default executable for the container ascurl
.CMD ["--version"]
: Specifies the default argument for theENTRYPOINT
, which, in this case, is--version
.
Next, save the Dockerfile, open a terminal, and make sure it’s in the directory where you saved the file. Now, run the following command to create an image named demo-app:v1
(you can name the image anything you prefer):
docker build . -t demo-app:v1
In the command above, the dot (.) after the build command indicates that the current directory (i.e. docker) is the build context. The -t
flag tags the Docker image with a given name, demo-app:v1
in this case.
After executing the command above, you’ll see an output similar to this:
Analyzing Docker Image Build Steps
Now, pay attention to the highlighted lines in the output above. As you can see in the first line, Docker took 23 seconds to build the demo-app:v1
image. We will discuss this build time further in an upcoming section on the Docker image layer cache.
Next, notice the two lines that start with [1/2]
and [2/2]
. The numbers indicate the current step and the total number of build steps involved in the build process. In our case, we had a 2-step build process. In step 1, Docker fetched and set up the base image alpine
as specified in our Dockerfile by the FROM alpine
command. In step 2, Docker executed the RUN apk update && apk add curl
command as specified in our Dockerfile. This command updated the package list and installed the curl
utility in the image.
But why are we talking about the build steps when we should be discussing image layers?
The two build steps above created two separate image layers. What about the other instructions in our Dockerfile? Didn’t they create any image layers? Yes, they did, but they created intermediate layers. Intermediate layers are 0B
in size and don’t add to the image size (more on this later).
Now, you might be wondering, how do I know which instruction creates image layers and which doesn’t? Here’s the answer: As a general rule, any Dockerfile instruction that modifies the file system creates a new layer. In our case, the instructions that started with LABEL
, ENTRYPOINT
, and CMD
directives didn’t modify the file system (they just added metadata or configuration to the image), so they didn’t add any layers that increased the file size of the Docker image.
Let’s now explore all the layers that our demo-app:v1
image is made up of. But before we do so, let’s confirm that the docker build
command that we executed earlier indeed created the demo-app:v1
image. Run the following command to see the list of Docker images on your system:
docker image ls
Executing the command above will display an output like this:
As you can see, the demo-app:v1
image is displayed in the list, confirming that we have the image in our system.
Viewing Docker Image Layers
In this section, we’ll explore how to view and analyze the layers of a Docker image using two commands: docker history
and docker inspect
.
Using the docker history command
To view the commands that create the image layers and the sizes they contribute to the Docker image, execute the following command:
docker history demo-app:v1
Upon executing the command, you’ll see an output like this:
Focus on two column headers: CREATED BY
and SIZE
. The CREATED BY
column shows the commands that were executed to generate the layers. And the SIZE
column indicates the size of each layer. As you can see, only two layers (as highlighted above) contributed to the image size. The remaining layers created intermediate layers and are 0B
in size.
It’s worth noting that the bottom two commands in the output correspond to the FROM alpine
instruction. For the other commands, there is a one-to-one mapping between the command and the instruction in the Dockerfile.
In summary, the demo-app:v1
image has two layers, equal to the number of build steps we saw in the output of the docker build
command. We can verify this using the docker inspect
command.
As a side note, the buildkit.dockerfile.v0
comment in the output indicates that the image layers were created using BuildKit, Docker's next-generation build system.
Using the docker inspect command
To confirm that the demo-app:v1
image has two layers, execute the following command:
docker inspect --format '{{json .RootFS.Layers}}' demo-app:v1
In this command, we’re using a Go (Golang) template to extract the layers' information. After running the command, you’ll get an output similar to the following:
We see the two layers of the demo-app:v1
image in the form of SHA256 hashes.
Note: All Docker images get a cryptographic content hash. This hash is referred to as the digest. The digest is based on the contents of the image layers and helps ensure the integrity and consistency of the image. When a Docker image is pulled or pushed, the digest is used to verify that the image has not been corrupted or tampered with.
Understanding Docker Image Layer Cache
In the previous two sections, we built a Docker image and examined its layers. But why should you care about these layers? Do they offer any benefits? To answer this question, we need to understand the concept of image layer caching and how it helps reduce build time.
Recall the time it took for Docker to build the demo-app:v1
image — 23 seconds. Now, ensure that your terminal is in the directory containing the Dockerfile, and then run the following command (note that we have not made any changes to the Dockerfile) to create an image called demo-app:v2
:
docker build . -t demo-app:v2
Executing the command above will display the following output:
Notice the highlighted section on the first line. The build time is just 4 seconds, significantly lower than the original build time of 23 seconds. How did Docker manage to build the demo-app:v2
image so quickly?
When we attempted to build a Docker image for the second time without making any changes to the Dockerfile, Docker intelligently realized that it already had copies of the image layers it was trying to build. Therefore, Docker didn’t rebuild any image layers it had previously built. Instead, it utilized the docker image layers stored in the cache, accelerating the build process.
Now you understand how Docker utilizes cached image layers to significantly reduce build times and improve overall efficiency. This is especially useful when working with large Docker images, where the build process can take a considerable amount of time.
If you're interested in learning more about Docker and how it can help you streamline your development workflow, be sure to check out the following Docker Learning path.
Conclusion
In this blog post, you learned about Docker image layers, docker image layer cache, and how Docker leverages caching to speed up the build time. Armed with this understanding, you’re now well-equipped to use Docker more efficiently.
More on Docker:
- How to Pass Environment Variables to Docker Containers
- How to Build a Docker Image With Dockerfile From Scratch
- How to Clear Docker Logs for a Container
- How to Keep Docker Container Running
- How to Fix Docker Build Requires Exactly 1 Argument
- How Dockerfile Works
- How to Use Docker CP Command With Examples
- How to Tag a Docker Image
- Docker Entrypoint vs. CMD: What Is the Difference and How to Choose