Skip to content

Keeping docker images small

It is important to keep docker images small. The smaller the image is, the faster it will be pulled, speeding up deployments, both in production and development environments. In addition, bigger images get deleted from the nodes cache sooner. The maximum size for an image stored in Rahti 2's internal registry is 5GB. Images over 1GB are already considered very big images.

Be mindful about what is added to the image

The first way to keep an image small is to by simply not adding unnecessary files. When developing is common to install full sources of libraries or other dependencies, for example the Linux kernel headers. These sources are not anymore necessary in production deployments.

Keep data out of the image

Images should only contain the application's runtime. This means that the data needed to run the application should not be added to the image. This way not only the image is smaller, but we avoid a rebuild when the data changes. The data can be stored in an external volume (PVC) that will be attached to the Pod upon startup, or it can be stored in Allas and downloaded during the startup or on demand when needed. Storing the data in Allas requires an extra logic in the application (or in a pre-load script) that understands where the data is and how to retrieve it. data can be stored in a external volume (PVC) that will be attached to the Pod upon startup, or it can be stored in

Reduce the number of layers

For every Dockerfile instruction (RUN, COPY, CMD, EXPOSE, ...), a new layer is added to the docker image. Each layer is stored as the difference from the previous one. This means that if some data is added in one layer, but removed in the next, the image will still contain said data. In order to see how much data each layer adds to the image, you may use:

docker history image_name

There are 3 approaches to reduce the number of layers:

  • Combine layers in the Dockerfile. Sometimes there are several RUN instructions one after the other. They can be easily combined by using &&:
RUN apt update

RUN apt install git

will become:

RUN apt update && \
    apt install git
  • Use multi stage builds (This feature was introduced in docker v17.05). The idea behind multi stage builds is to have several FROM commands in the same Dockerfile, each FROM starts a new stage in the build and does not carry the files from the previous stage, but allows copying files from the previous stages. The pattern used here is, to build the application in the first stage, and then in the second stage copy only the compiled application, leaving behind the sources and other compilation sub-products that we do not need to run the application. For example:
FROM golang:1.21
WORKDIR /src

RUN echo 'package main\n\
\nimport "fmt"\nfunc main() {\nfmt.Println("hello, world")\n}' >main.go

RUN go build -o /bin/hello ./main.go && cat main.go

FROM scratch
COPY --from=0 /bin/hello /bin/hello
CMD ["/bin/hello"]

The example was taken from the Use multi-stage builds article in Docker's documentation.

  • Use docker build --squash. Docker will squash the image into a single layer. This means that if your Dockerfile produces multiple layers modifying the same files, only the final state of the files will be stored. Similarly, if a file is created in one layer and later deleted in another, the file will not be stored in the image.

Use a small base image

When creating an image, choosing the base image is an important task. There are almost always multiple base images available for certain need. These base images can be very different in content, maintenance, security, size, etc. It is a good practice to evaluate the available images and do some comparisons between them. It is often not a good idea to take the first random image you find and put it to production. One base image that is very commonly used and has lots of documentation available is Alpine Linux.

Alpine Linux is a security-oriented, lightweight Linux distribution based on musl libc and busybox.

Currently the base image for Alpine (docker.io/alpine) is only 5.61 MB. For comparison, the sizes of Ubuntu's and CentOS' base images are 72.9 MB and 209 MB respectively. The biggest drawback that Alpine has versus other base images is that some applications are not compatible with musl libc and require glibc. Alpine will also have a smaller selection of software available in the repositories than Ubuntu or CentOS.

Use .dockerignore

.dockerignore works in the same way as gitignore does, creates a black list of files that will not be added to the image. This way is possible to exclude files that are not needed to run the application.