Hi, I'm Yoshi (@ymotongpoo). Are you enjoying building your container images for serverless and container runtime solutions in AWS? Many container runtime services exist in AWS, including Amazon ECS, Amazon EKS and AWS Lambda. The first step in using such container runtimes is to create a container image. AWS provides amazonlinux
images, and they are about 50 MB by default. Some people may be trying to reduce the size of the image in order to launch the container image as quickly as possible.
There are already many practices to reduce container size, but in this article, I would like to focus on the base image and discuss the characteristics of each. This article assumes that images are built based on Dockerfile. Unless otherwise noted below, image sizes are for the latest
tag for arm64 as of January 9, 2025.
TL;DR
I introduced scratch, busybox, alpine, Debian slim, UBI, Distroless, and Chainguard Image as base images for minimizing container size.
scratch
The most primitive way to create a minimal container image is to use an official Docker image called scratch
.
This scratch image is used in the bottom layer of many container images. For example, the hello-world
image that they usualy use to check if Docker is working properly is created with the following Dockerfile (where hello
is the executable binary included in the repository where the image is created.)
FROM scratch
COPY hello /
CMD ["/hello"]
scratch
, to put it bluntly, only contains nothing and is only available to talk to the kernel of the host OS, so you need to provide all the other necessary files by yourself. In exchange for the freedom to add the files you only need, you have to copy files at a very primitive level (there is no package manager, of course). If you want to create an application environment, you will need to copy a lot of files, including dependent libraries, and it will be difficult to put everything in a Dockerfile. In many cases, you will need to use a container image that ships with some common dependency libraries.
busybox
The busybox
container is, as the name suggests, a base image that contains the minimum environment for BusyBox to work. It is also one of the official Docker images. The actual Dockerfile for creating this image is as follows:
FROM scratch
ADD busybox.tar.gz /
CMD ["sh"]
You can see that busybox.tar.gz
1 is simply extracted on top of the scratch image we introduced earlier.
Although busybox
uses ash, it can use shells, is small (1.76MB), and has a choice of standard C libraries (libc): glibc and musl 2. This image is an option if you just want to run a simple shell script or a binary that runs by itself.
However, there is no package manager, so if you want to install libraries and so on, you have to copy them in the same way as scratch
. You may need to do a lot of work to write a Dockerfile-based description.
alpine
alpine
is a base image using a very lightweight Linux distribution called Alpine Linux, which includes BusyBox and musl environments, as well as a package management tool called APK (Alpine Package Manager). The image size is 3.81MB, which is very lightweight. Many of you are probably using alpine as a base image to create container images.
Although alpine
is lightweight and the APK makes it easy to create an environment, its use of musl in the libc makes it inconvenient in many situations. For example, there are the following inconveniences:
- Does not support extensions provided by glibc
- There are thread-ussafe cases when running on musl
- Difficulty in handling shared libraries (musl can't do lazy binding and can't unload)
- Small thread stack size, easy to encounter segmentation violations
I have had experienced cases where Python and other programs didn't work well with alpine.
Because of these troubles, it is my understanding that the preferences of the base image is now diverse as follows, assuming to use glibc for libc:
- a slightly larger image that includes a shell and package manager and can be easily described in a Dockerfile
- a very small image with only the necessary configuration for each individual use
Debian slim
debian:xxx-slim
is part of Debian's official Docker images and, as the name suggests, is a Debian-based image with a small image size. The shell is bash by default, and you can freely install stable packages released by Debian via apt
command.
With an image size of 26.76 MB (stable-slim
), it is much larger than other minimum images, but considering that the runtimes of various languages themselves are tens to hundreds of MB, the image size would be affordable as an application image.
Debian slim images are used as the slim base image for official images of many languages due to its stability. (Footnote: Example on Python 3.13.1)
Red Hat Universal Base Image
While most of the official images released by the OSS project are Debian based, there is also demand for images from RHEL-based distributions. For this purpose, Red Hat offers an image called Universal Base Image (UBI).
-
redhat/ubi9-micro
: Minimal environment with almost nothing but bash and glibc, 6.33MB, similar to busybox. -
redhat/ubi9-minimal
: Same purpose as Debian slim, but with microdnf. 35.84MB.
There are two other image types, Standard and Init, but I don't detail them here because they are out of the scope of the main purpose of the minimum image.
Distroless
Debian slim and UBI are easy to use in the direction of creating a very small image while taking into account the use of a package manager, but on the other hand, they are too large to satisfy the demand to create a minimum image for a specific language environment. If you want to satisfy such a demand, Distroless image may be the right choice for you.
As of January 2025, the latest version of Distroless is based on Debian 12.4. This does not mean that it is based on Debian slim, but rather that it only extracts the deb files from the Debian repositories for the packages needed for scratch
.
The image will vary depending on the package it is in, but these are the two basic ones.
-
gcr.io/distroless/static-debian12
(less than 800KB): containsca-certificate
files,base-files
,tzdata
and other minimal packages -
gcr.io/distroless/base-debian12
(8.5MB): containsglibc
andlibssl
in addition tostatic-debian12
For example, you can create a very small image on top of static-debian12
with a Go executable binary that can run as a single binary without depending on libc. There are also images based on these two images for commonly used languages, such as the followings for Java and Python.
-
gcr.io/distroless/java17-debian12
(88MB): containsbase-debian12
plus OpenJDK 17 and its dependencies distributed by Debian -
gcr.io/distroless/java21-debian12
(68MB): contains Temurin OpenJDK 21 and its dependencies in addition tobase-debian12
-
gcr.io/distroless/python3-debian12
(21MB): containspython3.11-minimal
and its dependencies in addition tobase-debian12
Since these images do not include the shell, if you really want to use the shell during development, for example, there is a debug
image for each of these images. This can be done by specifying the tag debug
(e.g. gcr.io/distroless/base-debian12:debug
) for the above images, and you will get an image with busybox
installed in addition to the environment.
Chainguard Images
As the name suggests, these are publicly available container images from Chainguard. Because Chainguard is a company specialising in container security and supply chain security, these container images have security measures in place in many ways, and the images they distribute advocate "low-to-no CVEs".
Chainguard Images is greatly influenced by Distroless, which is introduced above, and while following the essence of Distroless, it has developed its own unique style. For example:
- OSS distribution for container image creation called Wolfi OS
- apko, an new OCI container image builder
- Developed our own apk package builder called melenge
By taking full advantage of these, they are creating a minimum size container with only packages that are always provided by SBOM and instantly patched for security. The publicly available images they provide, for example, if you want something like busybox
or Distroless' base
image, are as follows
-
cgr.dev/chainguard/glibc-dynamic
(4.13MB) -
cgr.dev/chainguard/busybox
(3.59MB)
If you want a small base for additional packages like alpine
, or a minimum image for each language like Distroless, you can use the following images:
-
cgr.dev/chainguard/wolfi-base
(5.40MB) -
cgr.dev/chainguard/python
(20.9MB) -
cgr.dev/chainguard/jdk
(104.1MB)
Chainguard Images are awesome, but one point to note is that only images classified as Developer Images are freely available. For example, the images listed above are only freely available under the latest
tag, and if you want to stick to a specific version, you need to use Chainguard's paid-for service.
Conclusion
In this article, we introduced various base images for container image minimization. There is a wide range of configurations required to create container images when running applications, as many different conditions need to be taken into account. In addition to Docker, there are also other container creation tools such as Buildah, Buildpacks, Bazel, ko, kaniko, etc., that can be used for a variety of purposes. I hope this article will be helpful in determining the best base image for your development process and usage.
Top comments (2)
@ymotongpoo
Not many people talk about base image. So thank for sharing this.
A quick observation
did you forgot to omit the Japanese Language or is it intentional in the
Debian slim
sectionThank you for catching that! I just removed the leftover :)