Minifying container images with DockerSlim
Dieting
Although not always the first choice, Docker Engine will be used extensively as the default container runtime in Kubernetes and OpenShift on cloud-native infrastructure for some time to come. So, what would you say if I told you that you could automatically reduce the size of your Docker images 30x? Why are smaller container images a good thing? When it comes to containers, less is more: Your containers start quicker, they contain fewer security threats and bugs, they take up less storage, and they take less time to download and upgrade. In other words, it's a really good idea to keep your container images trim.
A sophisticated piece of open source software allows you to do just that; it's called DockerSlim [1]. The clever tool also makes a welcome foray into further securing your container images. The DockerSlim docs declare: "Don't change anything in your Docker container image and minify it by up to 30x (and for compiled languages even more) making it secure too!"
As well as clear and useful docs in the GitHub page README file, you'll also appreciate the slick introduction on a single-page website [2] that has a simple and effective example (before pointing you back to the GitHub repository again). There, you'll also find a useful video that runs through some command-line options to get you started.
In this article, I look at how to automate the slimming of your container images and how to start tweaking the security profiles of your containers, as well.
Trimming the Fat
Before looking at how to get DockerSlim working, I'll show you a few examples on how trim you can expect certain container images to become. The GitHub page has some eye-watering benchmarks that you might expect to achieve with a number of languages. The Python examples in Listing 1 from the README file [1] are impressive, although Node.js, Ruby, Golang, Java, PHP, and other languages can be expected to achieve similar results.
Listing 1
Python Minify Results
from ubuntu:14.04 - 438MB => 16.8MB (minified by 25.99X) from python:2.7-alpine - 84.3MB => 23.1MB (minified by 3.65X) from python:2.7.15 - 916MB => 27.5MB (minified by 33.29X) from centos:7 - 647MB => 23MB (minified by 28.57X) from centos/python-27-centos7 - 700MB => 24MB (minified by 29.01X) from python2.7:distroless - 60.7MB => 18.3MB (minified by 3.32X)
Teeny Tiny
Although you can install the binaries required to run DockerSlim directly, for obvious reasons (because you're dealing with container images), you should focus on using the dslim/docker-slim
Docker image that's been made available instead.
I'm using Linux Mint 19 (which uses Ubuntu 18.04 LTS under the bonnet), so to install Docker I'll use the command:
$ apt install docker.io
I would recommend, for anything other than testing, to use Docker CE [3], so you get the latest stable release with the latest features.
The super-slight DockerSlim provides lots of options, but to get started, I'll pull down one of the most popular images locally and try and minify it:
$ docker pull dslim/docker-slim [snip...] Status: Downloaded newer image for dslim/docker-slim:latest
Now that it is available locally, I'll pull down the nginx image, one of the most popular images in use today. In Listing 2, you can see the two images I pulled from Docker Hub and their disk sizes. In this case, nginx is coming in at an unwieldy 126MB.
Listing 2
Beginning Image Sizes
REPOSITORY TAG IMAGE ID CREATED SIZE dslim/docker-slim latest 2622a843b5f5 3 weeks ago 21.3MB nginx latest 231d40e811cd 4 weeks ago 126MB KE:
Having clicked the link for the latest version of the Nginx Dockerfile [4] on the Docker Hub website [5], I now have the ability to test DockerSlim.
By reputation, Nginx is not only a super-lightweight champion in the world of web servers, but because of the sheer number of downloads it experiences from Docker Hub (and therefore the scrutiny it is under), I would expect it to be extremely trim already. As a result, running it past DockerSlim's watchful eye is a big challenge.
The Dockerfile is too long (103 lines) to display here; I chose the "Raw" option for the Dockerfile on GitHub, copying its contents and then saving it locally to a file called "Dockerfile." From within the same directory as the downloaded Dockerfile (as root user, and not a less-privileged user, out of laziness mostly), the next command should do something interesting (Listing 3):
$ docker run -it --rm -v /var/run/docker.sock:/var/run/docker.sock dslim/docker-slim build nginx
Listing 3
Putting DockerSlim to the Test
docker-slim[build]: info=http.probe message='using default probe' docker-slim[build]: state=started docker-slim[build]: info=params target=nginx continue.mode=probe docker-slim[build]: state=image.inspection.start docker-slim[build]: info=image id=sha256:231d40e811cd970168fb0c4770f2161aa30b9ba6fe8e68527504df69643aa145 size.bytes=126323486 size.human=126 MB docker-slim[build]: info=image.stack index=0 name='nginx:latest' id='sha256:231d40e811cd970168fb0c4770f2161aa30b9ba6fe8e68527504df69643aa145' docker-slim[build]: info=image.exposed_ports list='80' docker-slim[build]: state=image.inspection.done docker-slim[build]: state=container.inspection.start docker-slim[build]: info=container status=created name=dockerslimk_1_20191223125148 id=d87e572be9182325c7d0af5cd672648a3ea13938013fd31c8627cf948d66015b docker-slim[build]: info=cmd.startmonitor status=sent docker-slim[build]: info=event.startmonitor.done status=received docker-slim[build]: info=container name=dockerslimk_1_20191223125148 id=d87e572be9182325c7d0af5cd672648a3ea13938013fd31c8627cf948d66015b target.port.list=[32770] target.port.info=[80/tcp => 0.0.0.0:32770] message='YOU CAN USE THESE PORTS TO INTERACT WITH THE CONTAINER' docker-slim[build]: state=http.probe.starting message='WAIT FOR HTTP PROBE TO FINISH' docker-slim[build]: info=continue.after mode=probe message='no input required, execution will resume when HTTP probing is completed' docker-slim[build]: info=prompt message='waiting for the HTTP probe to finish' docker-slim[build]: state=http.probe.running docker-slim[build]: info=http.probe.ports count=1 targets='80' docker-slim[build]: info=http.probe.commands count=1 commands='GET /' docker-slim[build]: info=http.probe.call status=200 method=GET target=http://172.17.0.3:80/ attempt=1 time=2020-11-11T12:52:01Z docker-slim[build]: info=http.probe.summary total=1 failures=0 successful=1 docker-slim[build]: state=http.probe.done docker-slim[build]: info=event message='HTTP probe is done' docker-slim[build]: state=container.inspection.finishing docker-slim[build]: state=container.inspection.artifact.processing docker-slim[build]: state=container.inspection.done docker-slim[build]: state=building message='building minified image' docker-slim[build]: state=completed docker-slim[build]: info=results status='MINIFIED BY 16.16X [126323486 (126 MB) => 7817018 (7.8 MB)]' docker-slim[build]: info=results image.name=nginx.slim image.size='7.8 MB' data=true docker-slim[build]: info=results artifacts.location='/bin/.docker-slim-state/images/231d40e811cd970168fb0c4770f2161aa30b9ba6fe8e68527504df69643aa145/artifacts' docker-slim[build]: info=results artifacts.report=creport.json docker-slim[build]: info=results artifacts.dockerfile.original=Dockerfile.fat docker-slim[build]: info=results artifacts.dockerfile.new=Dockerfile docker-slim[build]: info=results artifacts.seccomp=nginx-seccomp.json docker-slim[build]: info=results artifacts.apparmor=nginx-apparmor-profile docker-slim[build]: state=done docker-slim[build]: info=report file='slim.report.json'
Lo and behold, according to Listing 3, the line reporting
MINIFIED BY 16.16X [126323486 (126 MB) => 7817018 (7.8 MB)]
would be a remarkable achievement – if true. As shown in Listing 4, I check the Docker Engine output, which shows further confirmation of the DockerSlim report.
Listing 4
Output of docker images
REPOSITORY TAG IMAGE ID CREATED SIZE nginx.slim latest 3557e77e8e91 5 minutes ago 7.82MB dslim/docker-slim latest 2622a843b5f5 3 weeks ago 21.3MB nginx latest 231d40e811cd 4 weeks ago 126MB
The remarkable 7.82MB nginx.slim
image needs testing now, so I'll fire up a container with that image and see what happens (hit Ctrl+C to kill the container):
$ docker run -it -p80:80 nginx.slim
By pointing my browser at TCP port 80 on localhost, I can see the very welcome and familiar result of running an Nginx instance (Figure 1). I think anybody would agree that the reduction from 126MB to less than 8MB (as seen in Listing 4) is a sight to behold.
You should test your container images carefully before deploying them into critical environments. The README file talks about debugging and checking the process, so you have some support from the documentation if an image ever misbehaves.
DockerSlim needs to talk to Docker Engine in a specific way; usually using the Unix socket is easiest. Therefore, the command I used to minify my Nginx image contained the -v /var/run/docker.sock:/var/run/docker.sock
option. The documentation says:
If you are using the docker-slim container make sure you run it configured with the Docker IPC information, so it can communicate with the Docker daemon. The most common way to do it is by mounting the Docker Unix socket to the docker-slim container.
It goes on to note that some services like GitLab won't expose the socket in the same way, so you can use environment variables such as DOCKER_HOST
to get around this, if required. I'm paraphrasing, so check out the docs for more details.
Super Lightweight Champion
Now that you're suitably impressed, I'll look at some of the (many) features that DockerSlim brings to the table, beginning with the DockerSlim output (see Listing 3 again). You'll note that DockerSlim has quite rightly spotted that TCP port 80 is exposed within the Dockerfile – because Nginx is a web server. There are further details on how to interact with DockerSlim when it runs in the docs, because you can connect to a temporary container that's used while DockerSlim weaves its magic trimming down an image to check that things are working as expected.
Buy this article as PDF
(incl. VAT)