If you have worked on docker and pulled an image from the docker hub or created an image from the docker file, you might have noticed that the image pull happens in multiple stages. In this blog post, I am going to talk about docker layers and will go over:
- What are the keywords in dockerfile
- what is the docker layer?
- We will understand how to pull a docker image from dockerhub
- we will go deep dive into a dockerfile and understand image layers.
- Finally, we will understand the layers in the official nginx image.
To follow along, I assume you have prior knowledge of docker.
Keywords in dockerfile
Before understanding the docker layer, let’s first understand keywords in Dockerfile.
Docker consists of many keywords, and each keyword in docker represents a layer while creating a docker container. Please find some of the most used keywords in the dockerfile.
ADD copies the files from a source on the host into the container’s own filesystem at the set destination. CMD can be used for executing a specific command within the container. ENTRYPOINT sets a default application to be used every time a container is created with the image. ENV sets environment variables. EXPOSE associates a specific port to enable networking between the container and the outside world. FROM defines the base image used to start the build process. MAINTAINER defines a full name and email address of the image creator. RUN is the central executing directive for Dockerfiles. USER sets the UID (or username) which is to run the container. VOLUME is used to enable access from the container to a directory on the host machine. WORKDIR sets the path where the command, defined with CMD, is to be executed. LABEL allows you to add a label to your docker image.
For a Dockerfile to function, all keywords are not required. A user can create a dockerfile using some keywords.
What is the docker layer?
The layer in a dockerfile is the intermediate image created when building an image. A Docker image built up from a series of layers. Each layer represents an instruction in the Dockerfile image. Instruction like COPY, DEL, and RUN create a layer in the dockerfile.
Now Let’s understand docker layers by pulling an image from dockerhub.
Understand the docker layer by example
In this session, we will pull a simple alpine image from the dockerhub, understand how to create a dockerfile, and understand the layer with the help of the dockerfile.
To follow along, make sure docker is installed in your system. If not, follow this link to install docker in your system. Let’s understand this with a simple example. pull an alpine: latest image from the docker hub
➜ docker run alpine:latest Unable to find image 'alpine:latest' locally latest: Pulling from library/alpine 540db60ca938: Pull complete Digest: sha256:69e70a79f2d41ab5d637de98c1e0b055206ba40a8145e7bddb55ccc04e13cf8f Status: Downloaded newer image for alpine:latest
verify if the image gets downloaded.
➜ docker images REPOSITORY TAG IMAGE ID CREATED SIZE alpine latest 6dbb9cc54074 3 weeks ago 5.61MB
Now check the image history to get information about the layers present in the alpine image.
➜ ~ docker history alpine IMAGE CREATED CREATED BY SIZE COMMENT 6dbb9cc54074 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 3 weeks ago /bin/sh -c #(nop) ADD file:8ec69d882e7f29f06… 5.61MB
Let’s go to the official alpine image in the docker hub Alpine image
Click on the 3.15 tag, and it will take you to the GitHub Docker image file. I have pasted the same yaml file below as well.
FROM scratch ADD alpine-minirootfs-3.13.5-x86_64.tar.gz / CMD ["/bin/sh"]
Let’s now understand how this dockerfile is written.
- This dockerfile uses scratch as the base image.
- In the next step, an alpine-minirootfs-3.13.5-x86_64.tar.gz tar file to the image root folder
- And when the container starts, “/bin/sh” command ran.
Let’s understand this with a simple diagram.
- The Above docker file contains basically 2 layers.
- The first layer is from the base image itself. Here we are using a base image as a scratch. This is a scratch image, so it does not contain any layers.
- So the first layer gets created when a tar file into the base image. When this operation completes, an intermediate docker container gets created.
- In the Next step, a new layer gets added for the CMD, which runs in another new container.
- The old image creates the final layer. Once a new layer is added to the container, the previous container gets deleted, and the final container will only be present.
Was this example pretty small is init? Now, let’s take a bigger dockerfile and debug the layer in that.
Understand docker layer by another example
Many companies use Nginx to load balance their workload. For this demo, let’s pull an Nginx image from the dockerhub.
➜ docker pull nginx Using default tag: latest latest: Pulling from library/nginx f7ec5a41d630: Pull complete aa1efa14b3bf: Pull complete b78b95af9b17: Pull complete c7d6bca2b8dc: Pull complete cf16cd8e71e0: Pull complete 0241c68333ef: Pull complete Digest: sha256:75a55d33ecc73c2a242450a9f1cc858499d468f077ea942867e662c247b5e412 Status: Downloaded newer image for nginx:latest docker.io/library/nginx:latest
Once the image gets successfully pulled, let’s verify the image history to get the layer details.
➜ docker history nginx IMAGE CREATED CREATED BY SIZE COMMENT 62d49f9bab67 3 weeks ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B <missing> 3 weeks ago /bin/sh -c #(nop) STOPSIGNAL SIGQUIT 0B <missing> 3 weeks ago /bin/sh -c #(nop) EXPOSE 80 0B <missing> 3 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["/docker-entr… 0B <missing> 3 weeks ago /bin/sh -c #(nop) COPY file:09a214a3e07c919a… 4.61kB <missing> 3 weeks ago /bin/sh -c #(nop) COPY file:0fd5fca330dcd6a7… 1.04kB <missing> 3 weeks ago /bin/sh -c #(nop) COPY file:0b866ff3fc1ef5b0… 1.96kB <missing> 3 weeks ago /bin/sh -c #(nop) COPY file:65504f71f5855ca0… 1.2kB <missing> 3 weeks ago /bin/sh -c set -x && addgroup --system -… 63.9MB <missing> 3 weeks ago /bin/sh -c #(nop) ENV PKG_RELEASE=1~buster 0B <missing> 3 weeks ago /bin/sh -c #(nop) ENV NJS_VERSION=0.5.3 0B <missing> 3 weeks ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.19.10 0B <missing> 3 weeks ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0B <missing> 3 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B <missing> 3 weeks ago /bin/sh -c #(nop) ADD file:c855b3c65f5ba94d5… 69.3MB
Now try to co-relate the image layer with the official docker file of Nginx.
The Nginx dockerfile looks like this:
# # NOTE: THIS DOCKERFILE IS GENERATED VIA "update.sh" # # PLEASE DO NOT EDIT IT DIRECTLY. # FROM debian:buster-slim LABEL maintainer="NGINX Docker Maintainers <[email protected]>" ENV NGINX_VERSION 1.19.10 ENV NJS_VERSION 0.5.3 ENV PKG_RELEASE 1~buster RUN set -x \ # create nginx user/group first, to be consistent throughout docker variants .......... ........... # create a docker-entrypoint.d directory && mkdir /docker-entrypoint.d COPY docker-entrypoint.sh / COPY 10-listen-on-ipv6-by-default.sh /docker-entrypoint.d COPY 20-envsubst-on-templates.sh /docker-entrypoint.d COPY 30-tune-worker-processes.sh /docker-entrypoint.d ENTRYPOINT ["/docker-entrypoint.sh"] EXPOSE 80 STOPSIGNAL SIGQUIT CMD ["nginx", "-g", "daemon off;"]
In the Nginx image, debian:buster-slim is used as a base image. The debian:buster-slim dockerfile looks like this:
FROM scratch ADD rootfs.tar.xz / CMD ["bash"]
The Debian image is created from the scratch image, the rootfs file is added from the root directory, and finally, the start “bash” command runs.
So in the Nginx image, the first two layers have come from the base image.
<missing> 3 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B <missing> 3 weeks ago /bin/sh -c #(nop) ADD file:c855b3c65f5ba94d5… 69.3MB
And after that, a new layer will be created for each subsequent operation.
CMD ["nginx" "-g" "daemon… STOPSIGNAL SIGQUIT EXPOSE 80 ENTRYPOINT ["/docker-entr… COPY file:09a214a3e07c919a… COPY file:0fd5fca330dcd6a7… COPY file:0b866ff3fc1ef5b0… COPY file:65504f71f5855ca0… RUN .... && addgroup --system -… ENV PKG_RELEASE=1~buster ENV NJS_VERSION=0.5.3 ENV NGINX_VERSION=1.19.10 LABEL maintainer=NGINX Do…
I have created a diagram to explain the layers corresponding to the dockerfile. Refer to this diagram to understand the layers
Now we have a pretty good idea about the docker layers. The next question that should come to your mind will be why it is important to check for layers while creating a dockerfile.In the below session, we will try to understand why we should always look for layers while creating a dockerfile.
Importance of docker layer in dockerfile
While creating a dockerfile, our primary goal is to keep the image size as less as possible. A Docker image takes up more space for every layer you add to it. Therefore, the more layers the image has, the more space the image requires.
Let’s consider a scenario where you have created the below docker file
# # NOTE: THIS DOCKERFILE IS GENERATED VIA "update.sh" # # PLEASE DO NOT EDIT IT DIRECTLY. # FROM debian:buster-slim LABEL maintainer="XXX Docker Maintainers <[email protected]>" RUN apt-get update RUN apt-get install -y RUN test-package
In the above dockerfile, we have 3 RUN commands, which will add three layers in the Dockerfile, leading to a bigger image size. To avoid such a scenario, we can rewrite our dockerfile as below:
# # NOTE: THIS DOCKERFILE IS GENERATED VIA "update.sh" # # PLEASE DO NOT EDIT IT DIRECTLY. # FROM debian:buster-slim LABEL maintainer="XXX Docker Maintainers <[email protected]>" RUN apt-get update && apt-get install -y && test-package
In the above example, we have twisted the dockerfile to reduce the number layer, which unlimitedly leads to a smaller dockerfile.
Congratulations, I hope you now have a basic understanding of layers in docker. You have also learned how to check the layers from images pulled from dockerhub. We also understand the complex docker layer by taking the example of the official Nginx image, and finally, we understood the significance of docker layers.
Please let me know if you enjoyed this article in the comment box.