Introduction

In the first two parts of this series, we covered the most common methods to optimize Docker image size. We saw how multi-stage builds, combined with Alpine-based images, and sometimes static builds, would generally give us the most dramatic savings. In this last part, we will see how to go even farther. We will talk about standardizing base images, stripping binaries, assets optimization, and other build systems or add-ons like DockerSlim or Bazel, as well as the NixOS distribution.

We’ll also talk about small details that we left out earlier, but are important nonetheless, like timezone files and certificates.

Common bases

If our nodes run many containers in parallel (or even just a few), there’s one thing that can also yield significant savings.

Docker images are made of layers. Each layer can add, remove, or change files; just like a commit in a code repository, or a class inheriting from another one. When we execute a docker build,each line of the Dockerfile will generate one layer. When we transfer an image, we only transfer the layers that don’t already exist on the destination.

Layers save network bandwidth, but also storage space: if multiple images share layers, Docker needs to store these layers only once. And depending on the storage driver that you use, layers can also save disk I/O and memory, because when multiple containers need to read the same files from a layer, the system will read and cache these files only once. (This is the case with the overlay2 and aufs drivers.)

This means that if we’re trying to optimize network and disk access, as well as memory usage, in nodes running many containers, we can save a lot by making sure that these containers run images that have as many common layers as possible.

This can directly go against some of the guidelines that we gave before! For instance, if we’re building super optimized images using static binaries, these binaries might be 10x bigger than their dynamic equivalents. Let’s look at a few hypothetical scenarios when running 10 containers, each using a different image with one of these binaries.

Scenario 1: static binaries in a scratch image

  • weight of each image: 10 MB
  • weight of the 10 images: 100 MB

Scenario 2: dynamic binaries with ubuntu image (64 MB)

  • individual weight of each image: 65 MB
  • breakdown of each image: 64 MB for ubuntu + 1 MB for the specific binary
  • total disk usage: 74 MB (10x1 MB for individual layers + 64 MB for shared layers)

Scenario 3: dynamic binaries with alpine image (5.5 MB)

  • individual weight of each image: 6.5 MB
  • breakdown of each image: 5.5 MB for alpine + 1 MB for the specific binary
  • total disk usage: 15.5 MB

These static binaries looked like a good idea at first, but in these circumstances, they are highly counterproductive. The images will require more disk space, take longer to transfer, and use more RAM!

#images #docker #dockerslim #nixos #bazel

Docker Images : Part III - Going Farther To Reduce Images Size
1.55 GEEK