Let’s containerize a simple Go application that prints Hello, World! on the screen. While I am using Golang in this application, this concept is applicable universally, irrespective of the programming language.
The respective files for this example are present in the ch4/go-hello-world/single-stage directory within this book’s GitHub repository.
Let’s look at the Go application file, app.go, first:
package main
import “fmt”
func main() {
fmt.Println(“Hello, World!”)
}
The Dockerfile appears as follows:
FROM golang:1.20.5
WORKDIR /tmp
COPY app.go .
RUN GOOS=linux go build -a -installsuffix cgo -o app . && chmod +x ./app
CMD [“./app”]
This is standard stuff. We take thegolang:1.20.5 base image, declare a WORKDIR /tmp, copy app.go from the host filesystem to the container, and build the Go application to generate a binary. Finally, we use the CMD directive with the generated binary to be executed when we run the container.
Let’s build the Dockerfile:
$ docker build -t <your_dockerhub_user>/go-hello-world:single_stage .
[+] Building 10.3s (9/9) FINISHED
=> [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 189B 0.0s => [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/golang:1.20.5 0.6s => [1/4] FROM docker.io/library/golang:1.20.5@sha256:4b1fc02d… 0.0s => [internal] load build context 0.0s => => transferring context: 27B 0.0s
=> [2/4] WORKDIR /tmp 0.0s
=> [3/4] COPY app.go . 0.0s
=> [4/4] RUN GO111MODULE=off GOOS=linux go build -a -installsuffix cgo -o app . && chmod +x ./app 9.3s
=> exporting to image 0.3s
=> => exporting layers 0.3s
=> => writing image sha256:3fd3d261… 0.0s
=> => naming to docker.io/<your_dockerhub_user>/go-hello-world:single_stage
Now, let’s run the Docker image and see what we get:
$ docker run <your_dockerhub_user>/go-hello-world:single_stage Hello, World!
We get the expected response back. Now, let’s run the following command to list the image:
$ docker images
REPOSITORY
TAG
IMAGE ID
CREATED
SIZE
<your_dockerhub_user>
/go-hello-world
single_stage
3fd3d26111a1
3 minutes ago
803MB
This image is huge! It takes 803 MB to printHello, World! on the screen. This is not the most efficient way of building Docker images.
Before we look at the solution, let’s understand why the image is so bloated in the first place. We use the Golang base image, which contains the entire Go toolkit and generates a simple binary. We do not need the complete Go toolkit for this application to run; it can efficiently run in an Alpine Linux image.
Docker solves this problem by providing multi-stage builds. You can split your build into stages where you can build your code in one stage and then, in the second stage, export the built code to another context that begins with a different base image that is much lighter and only contains those files and components that we need to run the code. We’ll have a look at this in the next section.