Go Docker: Hello-Go with multi-stage build

Summary

This post covers a Hello-Go web application written in Go and hosted in a docker container. The solution will use Docker multi-stage builds to create the container image and display ‘Hello-Go’ on web requests.

Prerequisites

Description

Go is an open source project from Google that is based on the C programming language. It is a compiled language with multi-platform support across Linux, Windows, macOS and more. With the Go runtime performance and succinct syntax of its programming language, Golang, it is well suited for making a lightweight web application. The compiled application will be published as a Docker image so it can run on a container platform.

A Docker multi-staged build process will be used to separate the build and runtime images. Separating the build and runtime provides the benefit of removing non-essential runtime files and applications to reduce the image size. Removing the unrelated applications is also a security hardening component to reduce attack vectors for when the container is running.

The following process represents the subsystems involved in building the Hello-Go web application.

Process for Go source code to Docker image via multi-stage build

In this example we’ll build the image for the Linux kernel using Windows10 WSL2.

Steps

1. Using Windows Terminal, open a WSL Linux terminal (such as Ubuntu), create a source folder, hello-go, and open the folder in Visual Studio Code

Windows terminal with Ubuntu creating a source folder and open VSCode.

2. Create a source file named ‘hello-go.go’

VSCode with a hello-go.go source file

3. Enter the following code for the hello-go web application

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello-Go")
	})
	log.Fatal(http.ListenAndServe(":8080", nil))
}
Hello-go source code in VSCode

4. Create a docker file for the multi-stage build named ‘Dockerfile’ (no extension)

Dockerfile for docker image build

5. First section of the Dockerfile, we define the base image that will be used for the runtime image and expose the web application port the hello-go application is listening on, port 8080.

FROM alpine:latest as base
EXPOSE 8080

6. Next we define the build image that contains the Go compiler we can use to compile the .go source. We’ll create a /build folder and compile using the ‘go build’ command.

FROM golang:1.15.2-alpine3.12 as build
RUN mkdir /build
ADD . /build
WORKDIR /build
RUN go build -o hello-go .

7. With the application compiled, we can add the final stage by copying the build output to the runtime image defined at the beginning of the Dockerfile. We create an /app folder in the runtime image, copy the build to it, and specify the command to execute the application when the container runs

FROM base as final
RUN mkdir /app
WORKDIR /app
COPY --from=build /build .
CMD ["/app/hello-go"]
Complete Dockerfile entered with multi-stage build configuration

8. In Visual Studio Code, go to the Terminal menu and select New Terminal

9. Enter and run the following docker command to build and tag, hello-go:latest, the docker image

docker build -t hello-go:latest .

10. Verify the docker build completed successfully

Compilation success output in terminal after running Docker Build

11. Run the container and publish port 8080 on the host so it is accessible

docker run -d -p 8080:8080 hello-go:latest

12. Call the web application using ‘curl’

curl http://localhost:8080

13. Verify that the curl response displays ‘Hello-Go’

VSCode terminal window used to request the url with curl and displaying the Hello-Go output

14. List the build and runtime images to see the size differences

docker images | grep 'hello-go\|golang'
VSCode terminal output shows image sizes of the golang build and hello-go runtime images

15. Notice the size difference of 300MB for the build image, golang, and 12MB for the runtime image, hello-go.