One of the things I like the most about Go (golang) is the possibility of creating statically linked binaries that run without external dependencies. You can build a binary, put it in a scratch container image, and run it with minimal attack surface (You can’t hack into a reverse shell when there is no shell). For example, look at this (slightly simplified) Dockerfile I use to build my socket-proxy:

# syntax=docker/dockerfile:1
FROM golang:1.23.4-alpine3.21 AS build
WORKDIR /application
COPY . ./
RUN CGO_ENABLED=0 go build -tags=netgo -gcflags=all=-d=checkptr -ldflags="-w -s" -trimpath -o / ./...

FROM scratch
LABEL org.opencontainers.image.source=https://github.com/wollomatic/socket-proxy \
      org.opencontainers.image.description="A lightweight and secure unix socket proxy" \
      org.opencontainers.image.licenses=MIT
USER 65534:65534
VOLUME /var/run/docker.sock
EXPOSE 2375
ENTRYPOINT ["/socket-proxy"]
COPY --from=build ./socket-proxy /

This method works great until it comes to TLS encryption. At first, everything works. However, when it comes to the first TLS encrypted request, something breaks because of an invalid certificate. This error message may be confusing because the service you are trying to reach has a valid certificate. But the TLS root certificates (ca-certificates) are missing! Those are some of the few things not included in the Go binary.

Luckily, there are at least three possibilities to fix this issue:

1. Use a distroless image instead from scratch

The gcr.io/distroless/static-debian12 image may be a good place to start. For more information, see GoogleContainerTools/distroless.

2. Copy the ca-certificates from the build step

Copy /etc/ssl/certs/ca-certificates.crt into your final stage. Make sure you verify the source image. You´re copying their trusted ca-certificats!

# [... see above ...]
FROM scratch
# [... see above ...]
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=build ./socket-proxy /

3. Import the golang.org/x/crypto/x509roots/fallback package

I prefer importing the golang.org/x/crypto/x509roots/fallback package. You are in full control of the version in your binary, and a security checker like govulncheck could easily warn you if a poisonous certificate is included.

Please find the official documentation on pkg.go.devgolang.org/x/crypto/x509roots/fallback.

First, import the package:

go get golang.org/x/crypto/x509roots/fallback

Then, import it in your main.go or whatever your preferred filename is:

import _ "golang.org/x/crypto/x509roots/fallback"

Important quote from the original documentation:

This package must be kept up to date for security and compatibility reasons. Use govulncheck to be notified of when new versions of the package are available.

That is true. But now, you don’t need to rely on third parties to get trusted certificates.