DEV Community

Andri
Andri

Posted on • Originally published at andri.dk on

Slim Docker images for your Go application

How to build a slim Docker container for your Go application using multi-stage images.

I'm using Go 1.13 and the community module proxy to build the binary and Alpine as a base image. It adds a user and group instead of running as root.

Dockerfile

Be sure to replace "cmd/server/server.go" with your main file.

FROM golang:1.13 as builder

WORKDIR /app
COPY . /app
RUN CGO_ENABLED=0 GOOS=linux GOPROXY=https://proxy.golang.org go build -o app cmd/server/server.go

FROM alpine:latest
# mailcap adds mime detection and ca-certificates help with TLS (basic stuff)
RUN apk --no-cache add ca-certificates mailcap && addgroup -S app && adduser -S app -G app
USER app
WORKDIR /app
COPY --from=builder /app/app .
ENTRYPOINT ["./app"]
Enter fullscreen mode Exit fullscreen mode

I hope this is helpful. I mostly blogged this to document it for myself.

Discussion (11)

Collapse
victoravelar profile image
Victor Hugo Avelar • Edited on

Probably not for all the use cases, but you can also use the scratch container

FROM golang as base

WORKDIR /app

ENV GO111MODULE=on \
    CGO_ENABLED=0 \
    GOOS=linux \
    GOARCH=amd64

COPY go.mod .
COPY go.sum .

RUN go mod download

COPY . .

# it will take the flags from the environment
RUN go build

### Certs
FROM alpine:latest as certs
RUN apk --update add ca-certificates

### App
FROM scratch as app
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=base app /
ENTRYPOINT ["/app"]
Enter fullscreen mode Exit fullscreen mode

So its even smaller than alpine 😄

And it uses and caches modules so the download command will only be executed if something changed either on go.mod or go.sum

Hope it helps!

Collapse
krusenas profile image
Karolis

trying to remember what problems I faced with scratch image before I switched to alpine :/ Have you encountered any?

Collapse
therealkevinard profile image
Kevin Ard • Edited on

Scratch is still my go-to. Only the ssl thing handled here. That reeeeally angered me the first time when I pulled the perfectly crafted images into prod and every. call. failed.

Learned that lesson the hard way 🤷‍♀️

Update: I re-read this dockerfile and I like this one much more than mine. Alright if I borrow that? 😅

Thread Thread
krusenas profile image
Karolis

I think the problem I had was when I needed to do some external linking with C libs :) Then the first example wouldn't have worked either, you then need to use alpine as an initial build image too.

Thread Thread
therealkevinard profile image
Kevin Ard

Scratch is great, but it DEFINITELY depends on your workload. I haven't personally built anything that couldn't run in it, but I'm positive there are limits.

  • my rabbit work queues blaze in scratch.
  • pretty sure basic image manipulation would fail.

Such is scratch lol

Thread Thread
victoravelar profile image
Victor Hugo Avelar

Well I think your arguments are very valid, as I said it is not for all the use cases, still we are using it in prod for heavy load micro services and stream producers/consumers and so far its going surprisingly smooth, now I feel very lucky 😂

Thread Thread
therealkevinard profile image
Kevin Ard

Oh, don't get me wrong lol! My scratch-go images... Whoo! I have a single, polymorphic binary that blows my friggin socks off. It's a warehousing pipeline that can produce OR consume billions of records per day from who knows what sources. A damn tank, running on scratch.

Lol but I'd be nervous as crap deploying something that needs imagemagick via a scratch build 🤣

Collapse
victoravelar profile image
Victor Hugo Avelar

Nothing so far, at least not that I can remember right now. 🤔

Collapse
maxatome profile image
Maxime Soulé

Add -ldflags '-s -w' to go build to strip DWARF, symbol table and debug info. Expect ~25% binary size decrease.

golang.org/cmd/link/

Collapse
kcq profile image
Kyle Quest

I know it's an old discussion, but the multi-stage gotchas mentioned in this thread is still something you have to deal with. One of the main gotchas is that you need to know exactly what you need to copy. It gets tricky when it's more than just a single static binary. One potential alternative that takes care of this problem is DockerSlim. It creates a minified image for you using scratch and then it copies everything your application needs. Here's a Go example (no multi-stage build and it uses a standard ubuntu base image, but you still end up with a small image): github.com/docker-slim/examples/tr...

Collapse
biros profile image
Boris Jamot ✊ /

I really like multi-stages Dockerfiles for Go apps.