Multistage Docker Builds For Go Services

One thing I like about using Go for services is that it can have a much smaller footprint than other languages. This is particularly useful when building Docker images to run since a Docker container contains everything needed to run a service.

But images sizes can grow quickly. As Go code gets more complicated you can and up with a large number of dependencies and tests. Sometimes additional tools are needed as part of the build process such as for code generation. I also like to have various tools available when debugging build problems. But you can have all of that and still end up with small image by using multistage builds in Docker.

Multistage builds were added to Docker a couple of years ago and work very well. I will refer interested parties to the official documentation, but one issue I have encountered as I have used multistage builds is that sometimes the Go executable I create in a penultimate stage does not execute properly in the image created in the final stage. Typically the reason for this is because I use Ubuntu based images for building (Ubuntu is the Linux flavor I’m most comfortable using) and prefer Alpine based images for the final package to reduce size. So this is perhaps a problem of my own making, but I have found solutions to the common errors I see.

FROM golang:latest as builder 

# Generic code to copy, compile and run unit tests
WORKDIR /src
COPY src/ ./
RUN go test -v


FROM alpine:latest

WORKDIR /root/

# If you don't adde the certificates you get an error like:
# x509: failed to load system roots and no roots provided
RUN apk --no-cache add ca-certificates

# Because musl and glibc are compatible we can link and fix
# the missing dependencies.  Without this we get an error like:
# sh: ./main: not found
RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2

# Copy the go exec from the builder
COPY --from=builder /go/bin/main .

CMD [ "./main" ]

The above Dockerfile is a generic multistage build of a “main” go application. Here are couple things of note:
– The “RUN apk –no-cache add ca-certificates” snippet is necessary to avoid “x509” errors.
– The “RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2” snippet is a bit of a hack to get around the glibc vs musl in Ubuntu vs Alpine. Without it, you get unhelpful, difficult-to-debug errors like “sh: ./main: not found”.

An alternative to the library linking above is to instead set flags when compiling in Ubuntu like “CGO_ENABLED=0 GOOS=linux”.

When using multistage Docker builds for Go services, I’ve found the image for the first stage to be several hundred megabytes, but the image produced by the final stage will often be around a dozen megabytes. You can’t even fit a JVM in an image that small let alone a Java based service.

Meeting Time

My home is typically not quiet. Lately, with everyone home, it means there’s been even more noise. The kids are supposed to practice their various instruments daily which include piano, saxophone, trombone, and clarinet (not to mention the “bonus” ukulele and drum playing). Boy#2 is on a smoothie kick when means the blender is frequently running. Doors slam as kids run out to play in the yard and then come back in yelling about minor injuries. Conversations (calm and animated) abound.

A year or two ago I purchased an “On Air” sign online. The cord exited from the side so I modified it so instead the cord sticks straight out the back. It was intended to be hung by a chain (included), so I added some slots to be able to screw it directly to the wall. I then removed the plug and pushed the cord through a hole I drilled in the wall. The cord was rewired to an extension cord that plugs in to a simple “smart” plug. From there it was simple to hook it into our Alexa home ecosystem.

Now, when I have a meeting and need the house to be quieter-than-normal, I just say “Alexa, turn on broadcasting” (saying “turn on on air” sounded awkward to me) and the sign illuminates. But beyond that, my family is good enough to recognize the glowing red above my door and refrain from high audio behaviors.