Container - Smaller Images
Building your own containers is very easy and fast to learn. But very soon, you may end up with large container images and no idea how to reduce the footprint. Since you need to download and store each image, it can be useful to minimize the images, and this article will address exactly this.
Building your own containers is very easy and fast to learn. But very soon, you may end up with large container images and no idea how to reduce the footprint. Since you need to download and store each image, it can be useful to minimize the images, and this article will address exactly this.
Building Images
Building your own images is very common for most developers and operators. You want to add packages to a standard image or provide base images for your team. You may also want to package your application to deliver it to customers or deploy it on your infrastructure.
This tutorial uses Podman to demonstrate the build process, but it can be adapted with Docker, Kaniko, Containerd or Buildah, by replacing the commands.
If you never build an image before, you may want to read about it in the "Podman - Images" article, first. It provides an overview of the concept and process and explains what happens during the build process.
Smaller images
Reducing the footprint of container images can have a huge impact on your container workflow. Huge images will take some time to be updated, downloaded and started.
In addition, you need to provide proper disk space for your container infrastructure, which cannot be used for your data. This gets even worse in development environments, where you will download and start only slightly changed images over and over again.
But, this is also an issue in IoT environments, where you have limited disk space and bandwidth or in cloud environments, where you have to pay for every Gigabyte of data.
Smaller images also have a smaller attack surface. If you don't have something installed, it cannot be broken. Since you want to keep your images secure and patched, you will need to update more packages more often with larger images.
Demo application
For this tutorial, I want to have a very minimal Go-Application. It should respond with a simple "Hello, Community!". Building this application is quite easy. So, let's do it!
First, we will need a simple directory layout.
Create the above file "hello.go" in the "app" directory with the below content.
Now, we need to build this application and test, if it is working. This can be done in two ways.
If you have Go installed on your system, you can run the bellow commands.
# Create a go.mod file
$ go mod init hello
# Build the application
$ go build hello.go
# Run the application
$ ./hello
Hello, Community!
If you don't have Go installed, you can do the same, but with the help of containers.
# Create a go.mod file
$ podman run -it --rm -v $PWD:/usr/src/app:Z -w /usr/src/app docker.io/library/golang go mod init hello
# Build the application
$ podman run -it --rm -v $PWD:/usr/src/app:Z -w /usr/src/app docker.io/library/golang go build hello.go
# Run the application
$ ./hello
Hello, Community!
If this works fine, you can continue.
From LARGE to small
We will start with a pretty common example (building and running the demo application in a container), that you may use on your own. Each consecutive step will teach us something and reduce the image size.
Baseline
Let's start with something you find in many tutorials.
Create the Containerfile with the below content in our directory layout.
Now, let's build the container image.
# Build the container image
$ podman image build -t hello:latest .
# Run the resulting image
$ podman container run --rm hello:latest
Hello, Community!
And check the actual size.
# List images
$ podman image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/hello latest 0e2d43e2088f 25 seconds ago 1.09 GB
As you can see, 1009 MB is pretty huge.
Clean Up
Let's start with some cleanup. During an installation, Fedora will download metadata and caching data, which is still present in the image. Removing this immediately will reduce the resulting image size.
Adjusting the Containerfile as described below will do the trick.
Now, build, run and list the image again.
# Build the container image
$ podman image build -t hello:latest .
# Run the resulting image
$ podman container run --rm hello:latest
Hello, Community!
# List images
$ podman image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/hello latest 95d41ec9c4ee 12 seconds ago 842 MB
842 MB is already a bit better, but still huge.
Unwanted packages
Limiting the installed packages will also help to reduce the overall size of the resulting image. DNF supports two interesting switches for our purpose.
The flag --nodocs
will skip the installation of documentation packages, whereas --setopt install_weak_deps=False
suppresses the installation of weak dependencies.
The result will be even smaller again in most cases.
# Build the container image
$ podman image build -t hello:latest .
# Run the resulting image
$ podman container run --rm hello:latest
Hello, Community!
# List images
$ podman image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/hello latest a4f9bdc19aed 33 seconds ago 737 MB
But still, 737 MB seems too large for me.
Multi-stage builds (1)
For this example, where we want to build an application and only ship a resulting image, you can also use Multistage builds. The build of our Go application will be done in one container, and only the binary will be shipped in the resulting container.
In the first line, we are naming the build image. Afterwards, we will install Golang again and build our application. The new part will copy the resulting binary from our build image to a new image and set the proper command.
Let's see how the result looks like.
# Build the container image
$ podman image build -t hello:latest .
# Run the resulting image
$ podman container run --rm hello:latest
Hello, Community!
# List images
$ podman image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/hello latest 3a52f42328f6 2 minutes ago 161 MB
Pretty awesome, so far. Only 161 MB seems like a nice achievement.
Multi-stage builds (2)
With Golang, we can even go a step further. Golang applications will link all needed dependencies and libraries statically. In fact, we don't even need a Fedora environment (or any other) to run our application.
Let's reduce the size even more.
Our resulting image will be build FROM scratch
, but does this work? And how does the result look like?
# Build the container image
$ podman image build -t hello:latest .
# Run the resulting image
$ podman container run --rm hello:latest
Hello, Community!
# List images
$ podman image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/hello latest 6974dc3b38a5 7 seconds ago 1.94 MB
So, this is awesome. Only 1.94 MB as the result. Since we came from over 1 GB, I assume we achieved a lot.
In some future articles, I will demonstrate how you can do similar things with other software, like NodeJS or my lovely Apache httpd example. ;)
Docs & Links
The web is full of cool ideas and examples about Image builds and optimizations. Below you can find some articles and documentation. One of them is actually written from myself.
Conclusion
Working with containers can be really fun. And the next time you are building a container, you can reduce the footprint and surprise your community, colleagues and friends.
Do you know other cool technics to reduce the footprint? I would love to hear from it and learn something new.