Golang is a great language able to create statically linked binaries for different platforms such as Linux (ELF binaries) or Mac OS (Mach-O binaries). These binaries are often very small in size, and the language is getting increasingly popular in the microservices world because of their portability and the speed of deployment it enables: deploying a self-sufficient 10 MB Docker image on dozens of servers is just more convenient and fast than a 1.5 GB image full of libs. Golang and containers are two technologies that go perfectly well together, and shipping or managing infrastructures using Go programs is a breeze.
To step through this recipe, you will need the following:
Let's say our application code is checked in src/hello
. We'd like to begin by at least compiling the program, either for the Linux platform or for the Mac operating system.
We can compile our program sharing the code folder, and setting the work directory to it:
$ docker run --rm -v "${PWD}/src/hello":/usr/src/hello -w /usr/src/hello golang:1.7 go build -v
This way, even on a Mac OS system, we can generate a proper ELF binary:
$ file src/hello/hello src/hello/hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
That said, if we explicitly want a Mac binary, we can pass the standard Go environment variables GOOS
and GOARCH
so even a Linux machine can build a Mac binary:
$ docker run --rm -v "${PWD}/src/hello":/usr/src/hello -w /usr/src/hello -e GOOS=darwin -e GOARCH=amd64 golang:1.7 go build -v
Confirm we have a Mach-O executable and not an ELF binary:
$ file src/hello/hello src/hello/hello: Mach-O 64-bit executable x86_64
Now if we want to build our program right from a Dockerfile and generate a Docker image out of it, that would translate like the following:
FROM golang:1.7 COPY src/hello /go/src/hello RUN go install hello ENTRYPOINT ["/go/bin/hello"]
Just build that image and execute it:
$ docker build -t hello . $ docker run -it --rm hello
Now, it's a bit of a waste of space to have a 675 MB+ image for the very often small Golang application that often is only a few MB, and it takes time to deploy on servers. Here comes the scratch image: it just doesn't exist. We start from nothing, copy the binary, and execute it. Our build process (Makefile, build process, and CI) builds the app with the golang
image, but does not ship the compiled application with it, saving usually 95–99% of the space, depending on the size of our binary:
FROM scratch COPY src/hello/hello /hello ENTRYPOINT ["/hello"]
This generates the smallest image imaginable. Think only a few megabytes.
The main problem with the scratch image solution is the impossibility to debug it easily from inside the container, and the impossibility to rely on external libraries or dependencies such as SSL and certificates. Alpine Linux is this small image (~5 MB) that can greatly help us if we'd like to access a shell (/bin/sh
is available) and a package manager to debug our application. This is how we'd do it:
FROM alpine:latest RUN apk --update --no-cache add ca-certificates openssl && rm -rf /var/cache/apk/* COPY src/hello/hello /bin/hello ENTRYPOINT ["/bin/hello"]
Such an image usually is only a handful of megabytes more than the application binary, but helps greatly for debugging.
18.119.138.202