Docker containers (https://www.docker.com/resources/what-container) have become an industry standard for cloud-native applications due to their smaller size and resource need compared to the VM-based solutions. Since Docker 17, it is possible to use multistage builds (https://docs.docker.com/develop/develop-images/multistage-build/) to create small scratch-based containers.
Obvious advantages of this approach are the size of the resulting container as well as a reduced surface for potential attacks due to a very limited number of components. But does using scratch also have some unexpected “side-effects”? Let’s look into this using a small Golang-based Program.
This program just loads the HTTP response from google and prints the response status and body. It compiles and works as expected on a linux VM, so it should also work in a scratch-based container, right? Let's test it.
This Dockerfile copies the test.go file, compiles it and copies the resulting binary into a scratch-based container. If we now build and run this container, we are about to see the first surprise:
But what file are we missing? The /bin/test file? If you copy the contents of /bin to the local machine (there is no sh or ls on scratch), you will see that this file is indeed there. So what is going on? Looking into the Dockerfiles in examples that can be found on Stackoverflow or Medium, we can see that there is also CGO_enabled=0 which tells the Go compiler to not use C libraries for compilation. And exactly these libraries are the file(s) that are missing in our scratch-based container. So, let’s add this line and try again:
Let's use the modified Dockerfile above and see what happens.
What the ...? Panic? But why? It was just running locally ... Let's look closer at our program: client. Get returns two values, the response and the error. We looked at the response, but what if there is an error we have to handle first?
Now we check if there is an error first and then try to look at the response. Let's see what happens now:
Hm, but Google is trustworthy, right? Well, scratch does not have any trusted certificates, so by default no HTTPS communication is possible. Let's use our build container to fix this issue.
Using the Dockerfile above we can finally get the desired result:
So, what lessons can we learn from this (TL/DR)?
1. Scratch does not have C (or other) libraries which are available for most distro-based containers (not alpine though), so that you have to either provide or avoid them (as we did).
2. Scratch does not have trusted certificates which means that they have to be provided as well if use of SSL is desired.
2a. Proper error handling helps to find the real issues and solve them. :-)