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
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
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.
# [...] other stages
# Alpine for CA certs - need a decent set of CA certificates so outgoing TLS
# channels can successfully verify server identities.
FROM alpine as certs
# Build the final image
ENTRYPOINT [ "/app/myexecutable" ]
COPY --from=alpine /etc/ssl/certs /etc/ssl/certs
# [...] COPY and setup stuff from other stages
Trust doesn’t come from nowhere. We need to tell the libraries which CAs we trust, so certificate chains can be verified properly.