When crafting Dockerfiles for Debian-based containers, you'll frequently run into snippets that look like this:
# hadolint ignore=DL3008 RUN apt-get update -y \ && apt-get install -y --no-install-recommends \ inkscape \ && rm -rf /var/lib/apt/lists/*
This is what I like to call the
apt sandwich. I call it that because it
consists of whatever you're actually trying to do (e.g. install some packages),
apt-related boilerplate on either end.
apt sandwich looks like:
# hadolint ignore=DL3008 RUN apt-get update -y \ && apt-get install -y --no-install-recommends \ # ... # the sandwich filling - the packages you want to install # ... && rm -rf /var/lib/apt/lists/*
Here what is accomplished with this snippet:
apt-getis used instead of
apt, because of a long-present warning about
apt-get updateneeds to be run, because upstream maintainers clean out the
aptrepositories from base images to minimize the final image size.
rmrespectively are included to ensure that the installation completes without user intervention in the widest possible set of scenarios.
--no-install-recommendsis to further reduce container bloat.
rm -rf /var/lib/apt/lists/*cleans out the apt repository lists downloaded by
hadolintcomment on the first line prevents hadolint from complaining about linter requirements that are unsatisfiable. We don't control the versions of packages that exist in upstream distro repositories, so requiring version pinning of
aptpackages is generally an exercise in futility. This comment needs to precede the
All of this is done in a single command to keep intermediate files from being snapshotted by Docker and becoming a permanent part of the image.
apt sandwich doesn't just look nice—it also makes your containers
a lot smaller and build faster as well!
Don't take my word for it though. In this section, we have a list of sample
Dockerfiles you can build yourself to see the impact it can have, and we'll list
the image size and build time for each
Dockerfile. To follow
Save a Dockerfile with the given content and build the image:
docker build -t apt-sandwich .
This will print the time taken in the process:
$ docker build -t apt-sandwich . [+] Building 109.5s (6/6) FINISHED ... => [2/2] RUN apt-get update -y && apt-get install -y inkscape 106.6s ...
You can then retrieve the image size separately:
docker images apt-sandwich --format json | jq -r '.VirtualSize'
$ docker images apt-sandwich --format json | jq -r '.VirtualSize' 396.6MB
Okay, now you're ready to build some images!
Image size: 77.8MB.
FROM ubuntu:22.04 RUN apt-get update -y \ && apt-get install -y inkscape
Image size: 594MB. Build time: 106.6s.
Clean package index after install:
FROM ubuntu:22.04 RUN apt-get update -y \ && apt-get install -y inkscape \ && rm -rf /var/lib/apt/lists/*
Image size: 552MB. Build time: 100.8s.
Don't install recommended packages:
FROM ubuntu:22.04 RUN apt-get update -y \ && apt-get install -y --no-install-recommends inkscape \ && rm -rf /var/lib/apt/lists/*
Image size: 358MB. Build time: 68.2s.
To put it all together:
|Command||Size||Size Reduction||Time||Time Reduction|
|Clean package index after install||552MB||42MB (7%)||103s||3s (3%)|
|Don't install recommended packages||358MB||236MB (40%)||70s||36s (34%)|
Cleaning the package index and avoiding installing recommended packages has a pretty dramatic impact:
The image size is reduced by 236MB, or 40%!
The build time for this instruction is reduced by 36s, or 34%!
apt commands should pretty much always be written this way in a
Dockerfile. It will result in a lot of savings in size, bandwidth, and time,
which translates to savings in cost as well.
Containers will build faster, take up less space, and deploy into a target environment faster. And, since you know what this idiom is supposed to look like, it'll be easy to spot when a Dockerfile doesn't get it quite right, and you'll be able to quickly improve the container performance.