The apt sandwich
Brett Weir, March 2, 2023
Sandwich synopsis
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),
with apt
-related boilerplate on either end.
Sandwich specifications
The prototypical 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-get
is used instead ofapt
, because of a long-present warning aboutapt
CLI stability. -
apt-get update
needs to be run, because upstream maintainers clean out theapt
repositories from base images to minimize the final image size. -
The
-y
and-f
options ofapt-get
andrm
respectively are included to ensure that the installation completes without user intervention in the widest possible set of scenarios. -
--no-install-recommends
is to further reduce container bloat. -
rm -rf /var/lib/apt/lists/*
cleans out the apt repository lists downloaded byapt-get update
. -
The
hadolint
comment 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 ofapt
packages is generally an exercise in futility. This comment needs to precede theRUN
statement.
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.
Sandwich savings
Using the 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
along:
-
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!
-
Base image:
FROM ubuntu:22.04
Image size: 77.8MB.
-
Install inkscape:
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 |
---|---|---|---|---|
Install inkscape | 594MB | 106s | ||
Clean package index after install | 552MB | 42MB (7%) | 103s | 3s (3%) |
Don't install recommended packages | 358MB | 236MB (40%) | 70s | 36s (34%) |
Base image | 78MB |
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%!
Sandwich summary
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.