Recently at work I have been struggling with building a small/minimized Docker container of a Go app I have been working on. I started with
busybox but it has a major short coming… CA certificates. It isn’t trivial to get CA Certs on a
busybox container. This problem effectively prevents you from using SSL or TLS with your app… This is a non-starter…
I was doing some reading and I have seen a couple articles mention Alpine, its effectively a slimmed down version of
busybox but it makes it trivially easy to install packages, in my case CA Certificates! At this point I was really excited but I ran into some issues trying to compile the binary.
The solution is actually pretty simple, you need to build your Go app inside of a container and then copy the built container to your “production” Alpine image.
My solution uses the
Makefile, there is different ways of doing this but this is the simplest.
The first addition to the
Makefile is the following.
.PHONY: buildgo buildgo: CGO_ENABLED=0 GOOS=linux go build -ldflags "-s" -a -installsuffix cgo .
The above is intended to be run inside of a container but you can run it anywhere. What it does is builds a binary that is statically linked with CGO disabled so it uses the statically linked cgo binary on in the container. This is important as Alpine uses a custom lightweight subset of glibc called
The next stanza you need for your
Makefile actually builds two containers. One is your build container based on the
golang image and then copies the binary to your
.PHONY: builddocker builddocker: docker build -t yourorg/yourproject-build -f ./Dockerfile.build . docker run -t yourorg/yourproject-build /bin/true docker cp `docker ps -q -n=1`:/go/src/github.com/yourorg/yourproject/yourproject . chmod 755 ./yourproject docker build -t yourorg/yourproject .
To make the above work you need two Docker files. They are below…
Note the following is assuming your depencies are already downloaded, I do this with a make task,
.PHONY: deps deps: glide install
This is nice/elegant as you don’t have to run this in the container so if you have private repo’s you don’t need to copy in ssh keys or GitHub tokens to download source code. Just make sure your don’t have a
.dockerignore file as things in that file won’t be copied over…
FROM golang:1.7.1 WORKDIR /go/src/github.com/yourorg/yourproject/ ADD . /go/src/github.com/yourorg/yourproject/ RUN make buildgo CMD ["/bin/bash"]
FROM alpine COPY yourproject /go/bin/ RUN apk add --update --no-cache ca-certificates ENV GOPATH /go ENTRYPOINT ["/go/bin/yourproject"] # Service listens on port 6969. EXPOSE 6969
The above assumes your binary is named the same as your project name.
The magic in
Dockerfile is the ability to install the CA certs with a single command.
The only issue with disabling CGO is that some functionality in go requires it, most notably is from the
os/user package, the function
user.Current() makes use of it to determine a user’s home directory. You might get an error like the following…
user: Current not implemented on linux/amd64
The Apache Mesos project ran into this, you can see their solution. Their solution does a shortcoming though with their looping… Check out my change in the Go library for IBM Softlayer. The pull PR is available here.
Please follow me on Twitter at @jsloyer and follow me on Youtube!