DEV Community

Yoshi Yamaguchi for AWS

Posted on

Revisit base container image for AWS services

Small containers

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"]
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

You can see that busybox.tar.gz1 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:

  1. a slightly larger image that includes a shell and package manager and can be easily described in a Dockerfile
  2. 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): contains ca-certificate files, base-files, tzdata and other minimal packages
  • gcr.io/distroless/base-debian12 (8.5MB): contains glibc and libssl in addition to static-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): contains base-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 to base-debian12
  • gcr.io/distroless/python3-debian12 (21MB): contains python3.11-minimal and its dependencies in addition to base-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.


  1. The exact tarball is a modified version for each CPU platform. 

  2. uClibc is also available, but it is not used by many. 

Top comments (2)

Collapse
 
jatinmehrotra profile image
Jatin Mehrotra

@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 section

Debian slimイメージはその安定性から、多くの言語の公式イメージのslimベースイメージとして利用されています3。(脚注: Python 3.13.1での例)

Collapse
 
ymotongpoo profile image
Yoshi Yamaguchi

Thank you for catching that! I just removed the leftover :)