Creating a minimal container image for a Go application

Lucas Jellema

imageHow hard can it be – to create the smallest possible container image to run a Go application? It is not hard to create a container image that contains an binary executable file – and that is what a Go application turns into during the build process. However, that does not automatically produce a very small image or one that actually can run the application successfully. There are a few things to consider, as I found out during a few frustrating hours.

In the end it is all simple enough. When you know what to do, then it is all quite simple. Obviously.

What I had to learn:

  • a Go application built just like that in my Ubuntu environment will not run just like that in an Alpine based container image; it should not have been a surprise, but still I had not considered this
  • a Go application should be built with statically linked dependencies – to make runtime behavior not dependent on libraries being present (at all or in the expected location); you’ll enable a statically linked binary by setting CGO_ENABLED=0
  • the binary executable can be created a little smaller by stripping off the debugging symbols; this done using compile flags -ldflags=”-s -w”
  • in order to make HTTPS calls from the Alpine image it is required to have (recent) details for certificate authorities loaded into the container image

The Go build command I use to create the binary executable that will find its way into the container:

CGO_ENABLED=0 GOOS=linux go build -o person-producer -ldflags=”-s -w”

I run this command in the directory that contains the Go source file with the main package. person-producer is the name of the executable file that is the result of the build process; it is a standalone binary executable file of about 6MB.

The container image is built from a Dockerfile called `DockerfileAlpine`; it is located in the same directory as `person-producer.go`. It has the following content (to be processed to produce the small container image I am hoping for):

FROM alpine:latest

WORKDIR /app

# add ca-certificates to allow signed communications

RUN apk –no-cache add ca-certificates

# copy the application’s binary executable

COPY person-producer  ./

# run the executable when the container is started

CMD [“./person-producer”]

The container image is created from the latest *alpine* image. A directory called */app* is created and used for copying various files to. The file`person-producer` (the binary executable produced using `go build`) is copied to the container, to the work directory /app. The `RUN apk` line adds the required certificates to the image. The last line `CMD [“./person-producer”]` makes the application start up when a container based on this image starts running

Build the container image locally using this command:

docker build -t person-producer:1.0.0 -f DockerfileAlpine .

Building the container will take some time the first time because then the base image needs to be loaded. It will probably last less than 15 seconds on any subsequent iteration.

You can check the success of the build process by inspecting the command line output, using `docker images | grep person-producer` and by running a container based on the image:

docker run –name person-messenger -e STREAM_DETAILS_SECRET_OCID=$STREAM_DETAILS_SECRET_OCID person-producer:1.0.0

The size of the container image is about 12.2 MB.

Resources

My favorite build options for Go by Gaurav Kamathe – https://opensource.com/article/22/4/go-build-options

Compiling Your Go Application for Containers – Powerful Command-Line Applications in Go — by Ricardo Gerardi  – https://medium.com/pragmatic-programmers/compiling-your-go-application-for-co-ntainers-b513190471aa

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: