Imagine you want to compile a Go application, but you cannot do it locally. It might be that you want to cross-compile but you need CGO. Maybe you just want to test a new version of Go for compiling before taking the jump?
In this post, we'll show a way how to do this by using a Docker container.
The first step is to build a base image to start from. This base image can contain all the packages you need in addition to Go itself. For doing so, we start from the golang:1.11-alpine3.8 base image and build upon that. If prefer the Alpine version of the golang image as that's a lot smaller than the golang:1.11 image.
Dockerfile.base looks as follows:
FROM golang:1.11-alpine3.8RUN apk update && apk add gcc libc-dev make git
This uses Go 1.11 and additionally installs
git. If you want to build starting from this image (to avoid that you always have to rebuild the image from scratch), you can build it using the following command:
$ docker build --rm -t custom-go1.11:latest -f Docker.base .
This will output the following:
Sending build context to Docker daemon 335.2MBStep 1/2 : FROM golang:1.11-alpine3.8---> 20ff4d6283c0Step 2/2 : RUN apk update && apk add gcc libc-dev make git---> Running in a2a95ff75ab6fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/main/x86_64/APKINDEX.tar.gzfetch http://dl-cdn.alpinelinux.org/alpine/v3.8/community/x86_64/APKINDEX.tar.gzv3.8.0-94-gdea4c10014 [http://dl-cdn.alpinelinux.org/alpine/v3.8/main]v3.8.0-92-g87a3f3ec11 [http://dl-cdn.alpinelinux.org/alpine/v3.8/community]OK: 9542 distinct packages available(1/20) Installing binutils (2.30-r5)(2/20) Installing gmp (6.1.2-r1)(3/20) Installing isl (0.18-r0)(4/20) Installing libgomp (6.4.0-r8)(5/20) Installing libatomic (6.4.0-r8)(6/20) Installing pkgconf (1.5.3-r0)(7/20) Installing libgcc (6.4.0-r8)(8/20) Installing mpfr3 (3.1.5-r1)(9/20) Installing mpc1 (1.0.3-r1)(10/20) Installing libstdc++ (6.4.0-r8)(11/20) Installing gcc (6.4.0-r8)(12/20) Installing nghttp2-libs (1.32.0-r0)(13/20) Installing libssh2 (1.8.0-r3)(14/20) Installing libcurl (7.61.0-r0)(15/20) Installing expat (2.2.5-r0)(16/20) Installing pcre2 (10.31-r0)(17/20) Installing git (2.18.0-r0)(18/20) Installing musl-dev (1.1.19-r10)(19/20) Installing libc-dev (0.7.1-r0)(20/20) Installing make (4.2.1-r2)Executing busybox-1.28.4-r0.triggerOK: 114 MiB in 34 packagesRemoving intermediate container a2a95ff75ab6---> 15cde1650813Successfully built 15cde1650813Successfully tagged custom-go1.11:latest
You can then use the following command to verify that the image was built:
$ docker images listREPOSITORY TAG IMAGE ID CREATED SIZEcustom-go1.11 latest 15cde1650813 14 minutes ago 423MB
Now that the image is built, you can use that for compiling your Go app. Instead of creating a separate
Dockerfile which is then also used to run the app, we are going to use the
docker run command instead:
$ docker run --rm -v `pwd`:/go -w="/go" --ldflags '-extldflags "-static"' -e GOARCH=amd64 -e GOOS=linux custom-go1.11 make build
Let's decompose the command and see what each argument does:
docker run: tells docker you want to run something inside a container
--rm: causes the container to be removed automatically after it exits
-v `pwd`:/go: this maps the current directory to the
/gopath inside the container. This should be your
$GOPATHif possible or at least contain a
-w="/go": sets the working directory in the container to
--ldflags '-extldflags "-static"': compiles the app as a static binary, avoiding problems with which
libclibrary is linked (the one on Alpine is different than the one on Ubuntu for example)
-e GOARCH=amd64: sets the
GOARCHenvironment variable to
-e GOOS=linux: sets the
GOOSenvironment variable to
custom-go1.11: we are using the
custom-go1.11image to run the command in
make build: in the
buildwill be run.
This will build the binary using the container and depending on where you place the binary, it will be on the host machine.
You can also use this technique if you don't want to install Go on your machine itself, but that's not really the advised way of working.