Go tip of the week: Containers from scratch?
The typical Go binary is static and self-contained. It has absolutely no dependencies on system libraries or pre-installed runtimes.
A Go binary is therefore an ideal basis for creating super-small containers. There is no need to put a complete Linux distribution underneath a Go app. If you take it to the extreme, you can create a Go container FROM scratch
.
But is this a good idea?
On a second glance, a container built from the scratch
image (literally a zero-size, zero-content “image”) has a number of deficiencies.
- No user management. A Go binary in a
scratch
container lives in a user-free world. Callingos/user.Current()
panics in ascratch
container. - Missing system folders. An app might assume to find usual paths in their usual places; for example,
/tmp
or$HOME
. Ascratch
image does not set up any paths or directories. - No auxiliary apps. A Go app may want to run auxiliary tools via
os.Exec()
that are missing from ascratch
image. - No CA certificates. If you are serious about running a web server, you need TLS certificates. Certificates are signed by a chain of issuer certificates, up to root certificates from a certificate authority (CA). None of these exist in a
scratch
image. - Your pure Go binary may still need C libraries. The two standard library packages
os/user
andnet
packages will call intolibc
on Linux systems if CGO is enabled at compile time. Setting CGO_ENABLED to 0 makes the two packages fall back to pure Go implementations of the user and DNS lookup functions, but the pure Go variants are less performant, so you might want to prefer thelibc
option. (Related: Building static binaries with Go on Linux - Eli Bendersky's website.)
This list is not exhaustive; there may be more obstacles depending on the app's needs.
So while Go binaries work well on scratch
images in theory, the lack of basic features can be prohibitive.
Options to resolve this:
1. Work around the limits
Most of the limitations can be worked around; for example, by adding CA certificates or required directories and files to the image.
2. Use a “distroless” image
Distroless is a set of container images that are trimmed down to the barest minimum possible. The smallest version, the static
image, is about 2MB in size and is made for statically linked binaries like Go binaries. The base
image adds commonly used shared libaries like libc
or openssl
.
3. Use small images like Alpine
Alpine is a Linux distribution with a small footprint (about 8MB as a container image) that ships most of the things you would expect from a Linux distro: file system, user management, system libraries, packet management, and more.
Bottom line: Unless the space requirements are super strict, even a Go binary benefits from container images that are more than just the scratch
image.