Slim Down an App Using a Runtime Base
How to build more secure apps using multi-stage builds with a runtime base image
Some images will allow you to separate the build stages such that you can compile the app with one image and then copy the final binary onto a minimal runtime base image such as static or glibc-dynamic to significantly reduce the size of the final container image. This technique produces images that are ultra-light, hardened, performant, and secure.
Example with Go
Go is a great test case to demonstrate the value of using a multi-stage build to slim down the final app. Go is a compiled language that is not optimized for use as a runtime. For this reason, it is recommended to use a multi-stage build. We will compile the binary using the Go image, then copy the binary into a minimal runtime base image.
Knowing which runtime base image to use depends on how your Go project was compiled:
- If compiled as a static app, use the static Go image.
- If compiled as a dynamically linked app, use the Glibc-Dynamic image.
Static runtime
About static Go applications
Static binaries are self-contained and do not rely on shared system libraries or runtime dependencies on the host system. As such, they are fully portable and avoid compatibility issues with C libraries. If the static Go binary is mounted on a static base image, it will produce a tiny image, that is as minimal as it gets.
In the example below, the flagCGO_ENABLED=0
is added to the Dockerfile to ensure that the compiled binary is statically linked. CGO is a Go tool that enables the creation of Go packages that call C code. When CGO is disabled, the resulting binary is statically linked.
Example
-
Authenticate to the Minimus registry. Learn more
-
In your project directory, save the code below to a Dockerfile:
Dockerfile example -
In your project directory, save the code below to a file and name it
hello-minimus.go
.This is a very simple script which prints “Hello from Minimus!” to the terminal. You can use your own script instead.
-
In your project directory, create a
go.mod
file. This file declares the modules and dependencies required by the project. In our case, the module set is only needed for testing purposes, so it’s very simple. -
Your project directory should now contain 3 files:
Dockerfile
hello-minimus.go
go.mod
-
From your project directory run the following command to build the custom image
hello-go
:The period
.
specifies the current directory as the build context. -
Spin up the image just created with this command:
Dynamic runtime
About dynamic Go applications
If your Go app needs to link to database drivers or other C integrations, you can compile a dynamically linked binary by setting the CGO_ENABLED=1
flag. Make sure the required C libraries are available in both your build and runtime environments.
In the example below, we will use the Minimus Glibc-Dynamic image as a runtime base for a simple web application.
Example
-
Authenticate to the Minimus registry. Learn more
-
In a new project directory, save the code below to a new Dockerfile:
Dockerfile example -
Download the example file
hello-minimus-web.go
and save it to your project directory. This is a very simple script that creates a webpage with several tabs so you can navigate between them. -
In your project directory, create a
go.mod
file. This file declares the modules and dependencies required by the project. In our case, the module set is only needed for testing purposes, so it’s very simple. -
Your project directory should now contain 3 files:
Dockerfile
hello-minimus-web.go
go.mod
-
From your project directory run the following command to build the image:
The period
.
specifies the current directory as the build context. -
Spin up the image we just created with this command:
-
Open your browser and go to http://localhost:8080/ to see the default welcome page.