A Go, Docker workflow

This is a little post from our development department. We at CrowdPatent focus on innovation but furthermore, we are highly interested in providing good solutions.

TL;DR use a Makefile, a simple Docker Image and Go’s power.

Developing Go applications is easy, and this blog post is not about the pros or cons of Go in comparison to XYZ. But, this blog post is about using Go and Docker to get a great developing workflow.

Using Makefiles

Makefiles are great, because every developer should know make and furthermore it is available on (nearly) each and every system. I’ve copied this approach from the great hashicorp dev-team. Checkout the repositories of vault terraform or consul, all of them are setup with a Makefile included.

Structuring your Makefile

The following Makefile is a good starting point. It manages the building and running of a Go application inside a Docker container.

default: test

setup:  
    go get github.com/mailgun/godebug

buildgo:  
    CGO_ENABLED=0 GOOS=linux go build -ldflags "-s" -a -installsuffix cgo -o main .

builddocker:  
    docker build --rm --tag=johannesboyne/godockersample .

buildp: buildgo builddocker

run: buildp  
    docker run \
        -e HELLO=world \
        -p 1337:1337 johannesboyne/godockersample

test:  
    go test -timeout=5s ./...

debug:  
ifndef $(instrument)  
    godebug run ${gofiles}
else  
    godebug run -instrument=${instrument} ${gofiles}
endif  

One could easily add another command like make push to push the Docker Image into it's repository.

The Dockerfile

The Dockerfile would look like:

FROM tianon/true  
EXPOSE 1337

COPY certs/certs /etc/ssl/certs/  
# other stuff
ADD main /

CMD ["/main"]  

Why am I using the tianon/true image, the scratch image should be a little bit smaller? Yes, it is. But AWS Elastic Beanstalk (single instance docker deployment) forbids the usage of the scratch image, because it is a reserved one. Because we're using Elastic Beanstalk for single Docker instances and the Amazon EC2 Container Service (ECS) for multi container instances, thus it is important for us to have the possibility to run docker images on EB as well.
But, the tianon/true image is a very small one too, with it’s 125 bytes total.

About the Dockerfile, Certs and Go.

Why adding a certs directory? Some people have already seen this error x509: failed to load system roots and no roots provided this happens if a Go App. tries to make a HTTPS request without having access to root SSL certificates. Fixing this issue is simple and one has two options, either mount the host’s certs to the container docker run -v '/etc/ssl/certs:/etc/ssl/certs' … or, and I prefer the later, keep the root certificates in the image. Since, no one can be sure that the needed root certificates are available on the host which is running our container.

How big is such an image you may ask? The Docker image has around 4.56 MB, and the Go binary 4.07 MB for the following Go source:

package main

import (  
    "fmt"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {  
    fmt.Fprintf(w, "Hello World %s", "Test")
}

func main() {  
    http.HandleFunc("/", handler)
    http.ListenAndServe(":1337", nil)
}

Thus, 89 % of the Docker image size exists because of the Go binary itself.