Frequently, after we create our Docker image using Dockerfile, we get images heavier than we expected and this ends with fulling our file system and storage. So, how can we make our Docker containers images lighter?

We are not going to define and talk about what exactly are the layers but as a very basic notion, Docker images are built by layers and these layers are the instructions written in the Docker file. Every instruction (line in a Dockerfile) is a layer and it has some weight depending on what’s doing.
# One layer, which also contains layers since it's an image
FROM docker.io/fedora
# Another layer
RUN dnf install -y httpd
# Another layer
CMD ["/usr/sbin/httpd", "-DFOREGROUND"]
Layers are immutable and additive, meaning that once a Layer has done its job, it cannot be deleted or modified.
The main goal here would be to use a very minimal base image, that only contains what we need. Nothing else. Usually you won’t find a base image that contains everything, so probably you may end up by adding some extra layers like installing some packages like in the previous example.
Optimizing the layers to make our Docker images lighter
If we build the previous image, we would see how Docker is building layer by layer and storing in into the cache for its previous usage.
terrorsys@Andromeda:~/TestLayers$ docker build -t testlayers .
[+] Building 39.3s (6/6) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 124B 0.0s
=> [internal] load metadata for docker.io/library/fedora:latest 3.2s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/2] FROM docker.io/library/fedora:latest@sha256:5ce8497aeea599bf6b54ab3979133923d82aaa4f6ca5 4.7s
=> => resolve docker.io/library/fedora:latest@sha256:5ce8497aeea599bf6b54ab3979133923d82aaa4f6ca5 0.0s
=> => sha256:5e22da79803c567fceb0e255f1168977259525a4279cb518016a60df025412fb 2.00kB / 2.00kB 0.0s
=> => sha256:d4df0db66c89d7e6225ce9d3597a045fb95c020f3174af1830df88a37a871db8 80.12MB / 80.12MB 3.7s
=> => sha256:5ce8497aeea599bf6b54ab3979133923d82aaa4f6ca5ced1812611b197c79eb0 1.25kB / 1.25kB 0.0s
=> => sha256:a0f4dffd30e0af6e53f57533e79a9e32699d37d8e850132ff89f612d6ea8a300 529B / 529B 0.0s
=> => extracting sha256:d4df0db66c89d7e6225ce9d3597a045fb95c020f3174af1830df88a37a871db8 1.0s
=> [2/2] RUN dnf install -y httpd 30.5s
=> exporting to image 0.7s
=> => exporting layers 0.7s
=> => writing image sha256:28bff713c5bc28c097fbae424ce9f4bb87226596736f04f7eb215aa92dbb4078 0.0s
=> => naming to docker.io/library/testlayers 0.0s
Finally we can see how the image is built and inspect the layers to see their weight.
terrorsys@Andromeda:~/TestLayers$ docker images | grep testlayer
testlayers latest 28bff713c5bc 8 seconds ago 386MB
terrorsys@Andromeda:~/TestLayers$ docker image history testlayers
IMAGE CREATED CREATED BY SIZE COMMENT
28bff713c5bc 16 seconds ago CMD ["/usr/sbin/httpd" "-DFOREGROUND"] 0B buildkit.dockerfile.v0
<missing> 16 seconds ago RUN /bin/sh -c dnf install -y httpd # buildk… 164MB buildkit.dockerfile.v0
<missing> 3 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 3 months ago /bin/sh -c #(nop) ADD file:b8701dca3d7c8dad1… 222MB
<missing> 11 months ago /bin/sh -c #(nop) ENV DISTTAG=f40container … 0B
<missing> 3 years ago /bin/sh -c #(nop) LABEL maintainer=Clement … 0B
Basically, we can see there layers coming from the base image, and the layers we added. But how about if we decrease the size of it to make the image lighter?
As we can see, since we are using “dnf install”, we are also caching so many other data we don’t really need. Accordingly on Fedora configuration, we can simply remove it by running “dnf clean all”.
However, as we saw recently, layers are additive and immutable, meaning that once the layer has been written and cached, we cannot modify it. It won’t matter if we add a new layer with that step, it won’t make any change.
Nevertheless, we can squash these three steps in an unique layer, so when the step finishes and save on cache, the size will be much lower. We can do that by modifying our Dockerfile.
FROM docker.io/fedora
RUN dnf install -y httpd && \
dnf clean all
CMD ["/usr/sbin/httpd", "-DFOREGROUND"]
Basically we can use “&&” to say “run this second command only if the first one was successfully done” and then by “\” we indicate a line jump, just to see our Dockerfile much more clear.
Once we have created again our image, we can see now that the size it’s 303 MB instead of 386 MB and the layer went from 164 MB to 81.3 MB. This may not seem like a huge improvement but take into consideration this two scenarios:
- If we have multiple layers and we clean all of them, the final result will be a much lighter image. In a production environment where we cannot add space as we need, this is crucial if we multiply this for many different images. We can save our disk.
- In environments where the network is congested, we need to reduce the size of our objects to allow a faster traffic.
Using the right base image
Another point here is the base image we are using to create our own. This one runs a Fedora-based image. But since we only want to run Apache, we can find in the Docker registry another lighter base image which already contains Apache: https://hub.docker.com/r/nimmis/alpine-apache This image for example is an Alpine (much lighter than Fedora) which already has the desired package.
So our Dockerfile would look like the following:
FROM nimmis/alpine-apache
CMD ["/usr/sbin/httpd", "-DFOREGROUND"]
Once we compiled it, we can see that the size of our image went from 303 MB to only 20.7 MB. This is much lighter size, which will improve our disk usage and network traffic to pull/push images. It’s ideal for production environments or other critical environments where we prioritize network and disk.
terrorsys@Andromeda:~/TestLayers$ docker images | grep testlayer
testlayers latest 311bb5eeeb68 2 years ago 20.7MB
If you liked this post, don’t forget to comment and read other posts regarding Docker.
Thanks for the post!