Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What Docker scratch contains by default?

There is an option to use FROM scratch for me it looks like a really attractive way of building my Go containers.

My question is what does it still have natively to run binaries do I need to add anything in order to reliably run Go binaries? Compiled Go binary seems to run it at least on my laptop.

My goal is to keep image size to a minimum both for security and infra management reasons. In an optimal situation, my container would not be able to execute binaries or shell commands outside of build phase.

like image 605
Kimmo Hintikka Avatar asked Dec 18 '22 04:12

Kimmo Hintikka


1 Answers

The scratch image contains nothing. No files. But actually, that can work to your advantage. It turns out, Go binaries built with CGO_ENABLED=0 require absolutely nothing, other than what they use. There are a couple things to keep in mind:

  • With CGO_ENABLED=0, you can't use any C code. Actually not too hard.
  • With CGO_ENABLED=0, your app will not use the system DNS resolver. I don't think it does by default anyways because it's blocking and Go's native DNS resolver is non-blocking.
  • Your app may depend on some things that are not present:
    • Apps that make HTTPS calls (as in, to other services, i.e. Amazon S3, or the Stripe API) will need ca-certs in order to confirm HTTPS certificate authenticity. This also has to be updated over time. This is not needed for serving HTTPS content.
    • Apps that need timezone awareness will need the timezone info files.

A nice alternative to FROM scratch is FROM alpine, which will include a base Alpine image - which is very tiny (5 MiB I believe) and includes musl libc, which is compatible with Go and will allow you to link to C libraries as well as compile without setting CGO_ENABLED=0. You can also leverage the fact that alpine is regularly updated, using its tzinfo and ca-certs.

(It's worth noting that the overhead of Docker layers is amortized a bit because of Docker's deduplication, though of course that is negated by how often your base image is updated. Still, it helps sell the idea of using the quite small Alpine image.)

You may not need tzinfo or ca-certs now, but it's better to be safe than sorry; you can accidentally add a dependency without realizing it breaks your build. So I recommend using alpine as your base. alpine:latest should be fine.

Bonus: If you want the advantages of reproducible builds inside Docker, but with small image sizes, you can use the new Docker multi-stage builds available in Docker 17.06+.

It works a bit like this:

FROM golang:alpine
ADD . /go/src/github.com/some/gorepo  # may need some go getting if you don't vendor
RUN go build -o /app github.com/some/gorepo

FROM scratch  # or alpine
COPY --from=0 /app /app
ENTRYPOINT ["/app"]

(I apologize if I've made any mistakes, I'm typing that from memory.)

Note that when using FROM scratch you must use the exec form of ENTRYPOINT, because the shell form won't work (it depends on the Docker image having /bin/sh, which it won't.) This will work fine in Alpine.

like image 196
John Chadwick Avatar answered Dec 30 '22 21:12

John Chadwick