Building Small Containers(Kubernetes Best Practices)
The first step in deploying to Kubernetes is putting your app inside a container. let’s explore how you can create a small and secure container images
Thanks to Docker, Creating a container image has never been simpler. Specify your base images. Add your changes and build a container. While this is great for getting started using a default
Base images can lead to larges full of security of vulnerabilities. Most docker images use Debian or Ubuntu as base images. While this is great for compatibility and easy onboarding. These base images can hundreds of MB addition overhead to your container.
For example, simple Node.js and Go, “hello world” apps are around 700 megabytes. while Your application is probably only a few megabytes in size. So all this additional overhead is wasted space and a great hiding place for security vulnerabilities and bugs.
Let’s look at two methods to reduce the container image size.
- Small Base Images
- Builder Pattern
Using smaller base images is probably the easiest way to reduce your container size. Chances are your language or stack that you are using provides an official image that’s much smaller than the default image.
For example, Let’s take a look at our Node.js container. Going from the default node:8 to node:8-alpine reduces our base image size by 10 times.
To move to a smaller base image, update your Docker a new base image. Old onbuild image, you need to copy your code into the container and install any dependencies.
In the new Docker file, the container starts with a directory for the code, install dependencies with NPM, and finally, starts the Node.js server. With this update, the resulting container is almost 10 times smaller.
If your programming language or stack for a small base image, you can build your container using raw alpine Linux as a starting point. This also gives you
complete control over what goes inside your containers.
A small base image is a great way to quickly build small containers. But you might be able to go even smaller using the builder pattern with interpretive
languages, the source code.
With interpretive languages, the source code is sent to an interpreter, and
then it is executed directly. But with a compiled language, the source code is turned into compiled code beforehand. Now, with compile languages,
the compilation step often requires tools that are not needed to actually run the code. So this means that you can remove these tools from the final container completely.
To do this, you can use the builder pattern. The code is built in the first container and then the compiled code is packaged in the final container without all the compilers and tools required to make the compiled code.
Let’s take a Go application through this process. First, let’s move from the
onbuild image to Alpine Linux. In the new Docker file, the container starts with a golang:alpine image. Then it creates a directory for the code, copies in the source code, builds the source, and then finally starts the app. This container is much smaller than the onbuild container, But it still contains a
compiler and other Go tools that we really don’t need.
Let’s extract just the compiled program out and put it into its own container. So you might notice something strange about this Docker file. It has two FROM lines. The first section looks exactly the same as the previous Docker file, except that it uses the AS keyword to give this step a name. In the next section, there is a new FROM line. This will start a fresh image, and instead of using golang:alpine, we will use raw alpine as the base image. Raw Alpine Linux doesn’t have any SSL certificates installed, which will make most API
calls over HTTPS fail. So let’s install some root CA certificates and now comes the interesting part. You can use the COPY command to copy the compiled code from the first container into the second. This line will copy just that one file and not the rest of the Go tooling. This new multistage Docker file contains a container image that’s just 12 megabytes which is quite smaller than the original image.
Advantages of smaller containers
Let’s look into the first advantage of smaller containers. it reduces times to build, push and pull images from container registry
For the initial build, you can see that the smaller container has
a huge advantage over larger containers. So there is a significant
time saving here. Fast images pulling is also going to help in case of node scaling in Kubernetes clusters.
People often say that containers are more secure if they’re smaller because they have less surface area for attacks.
As shown in the report there are only 3 medium vulnerabilities for the small container, But 16 critical and over 370 for the larger container. Larger container has more area of attacks than a smaller container.