Container images with golang from scratch
One of the things I like about golang (and Rust too, by the way) is that it’s
quite simple to build really small container images by statically linking the
executables, and using scratch
as the base image. I’ve done this a few times
in the past, and was doing it again just recently. Except that this time around,
I ran into issues: the container would crash soon after it started.
Examining the logs, it was easy to see why. Here’s an excerpt:
[...] tls: failed to verify certificate: x509: certificate signed by unknown authority
I need to add: the typical golang tools I’ve built and run from scratch
-based
containers in the past would not make requests out to the internet, this one did.
So the error should have been expected: after all, how could the HTTPS client know to trust the remote HTTPS server when it was running from an image that had no hints as to which CAs to trust?
Trust some CAs
The solution is simple: do what other base images do. Let the HTTP client libraries
know which CAs to trust. Since I’m already using a multi-stage build in my
Dockerfile
, I simply added one more stage to it. In the
golang crypto
library sources
you can see where it looks for trusted CA certificates. So we just put some
(from Alpine in this case) there. Depending on your needs, you may want to update
the package that installs the ca-certificates first, too. But it’s even easier
than that. The COPY
command nowadays also supports fetching files directly
from another image, as shown below. Here, the directory /etc/ssl/certs
is
copied directly from the alpine:latest
image.
# [...] other stages
# Build the final image
FROM scratch
USER 1000:1000
ENTRYPOINT [ "/app/myexecutable" ]
# Copy set of CA certificates from latest alpine image.
COPY --from=alpine /etc/ssl/certs /etc/ssl/certs
# [...] COPY and setup stuff from other stages
That’s all.
Summary
Trust doesn’t come from nowhere. We need to tell the libraries which CAs we trust, so certificate chains can be verified properly.