I've been tasked (more or less) with building the first Go service at $DAYJOB , which is almost exclusively a Python shop. Why Go of all languages? Well, some of my coworkers are big fans of the gopher, it's an easy language, it's attached to one of the big companies, and it's much faster than Python, so I feel more comfortable pushing for this rather than Rust or (sadly) Nix.
The project that recently fell onto my lap is basically RCE-as-a-service, and it just so happens that one of the few languages that I would feel comfortable letting users execute remotely on our servers has a Go implementation, which is as good an excuse as any to take a break from the usual snekslop.
I still haven't convinced anybody here to get on the Nix train, so after a short stint of building the project and the OCI images with a recursive cycle of Make and Nix, I breathed a heavy sigh and dropped it for Docker and Docker Compose, which is what we generally use here. And just between you and me, I don't think we use them very well. CI is painfully slow and we all just kinda live with it because figuring out how the damn Dockerfile works sucks even more.
Which is a shame, because Nix is pretty good at building OCI images. This is all the code you need to create a minimal image that contains only your service and nothing else, not even /bin/sh .
{ pkgs ? import
You can build that with nix-build docker-test.nix and inside ./result you'll find a script that generates the image's tarball. Load it up with ./result | docker load and Docker will report a new image called someimage:latest when you run docker image ls . It's only 45.8MB, and most of that is taken up by glibc.
But what if I told you that you can get more or less the same result with a simple Dockerfile if you know what you're doing? Especially if you're building a static executable, which Go famously does (at least when you set CGO_ENABLED=0 ) and if you're willing to sacrifice a few affordances. Who needs coreutils anyway?
In this post I'll show you a few tricks I found while striving to make small and fast-building images. I'm not an expert in Docker by any stretch so maybe you know all of this stuff already, but perhaps you might learn something new.
A barebones image
I'm using goose for database migrations. I configured the docker-compose.yml to run its container just after the database has started and before the actual service runs, and since it's a very small self-contained tool I figured I could try to make the image as small and quick to build as possible. Let me show you the relevant part of the docker-compose.yml first:
... continue reading