DEV Community

sudo pacman -Syu
sudo pacman -Syu

Posted on

Dockerfile for Go

Each time I start a new Go project, I repeat many steps.
Like set up .gitignore, CI configs, Dockerfile, ...

So I decide to have a baseline Dockerfile like this:

FROM golang:1.18beta1-bullseye as builder

WORKDIR /build

COPY go.mod .
COPY go.sum .
COPY vendor .
COPY . .

RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOAMD64=v3 go build -o ./app main.go

FROM gcr.io/distroless/base-debian11

COPY --from=builder /build/app /app

ENTRYPOINT ["/app"]
Enter fullscreen mode Exit fullscreen mode

I use multi-stage build to keep my image size small.
First stage is Go official image,
second stage is Distroless.

Before Distroless, I use Alpine official image,
There is a whole discussion on the Internet to choose which is the best base image for Go.
After reading some blogs, I discover Distroless as a small and secure base image.
So I stick with it for a while.

Also, remember to match Distroless Debian version with Go official image Debian version.

FROM golang:1.18beta1-bullseye as builder
Enter fullscreen mode Exit fullscreen mode

This is Go image I use as a build stage.

WORKDIR /build

COPY go.mod .
COPY go.sum .
COPY vendor .
COPY . .
Enter fullscreen mode Exit fullscreen mode

I use /build to emphasize that I am building something in that directory.

The 4 COPY lines are familiar if you use Go enough.
First is go.mod and go.sum because it defines Go modules.
The second is vendor because I use it a lot, this is not necessary but I use it because I don't want each time I build Dockerfile, I need to redownload Go modules.

RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOAMD64=v3 go build -o ./app main.go
Enter fullscreen mode Exit fullscreen mode

This is where I build Go program.
CGO_ENABLED=0 because I don't want to mess with C libraries.
GOOS=linux GOARCH=amd64 is easy to explain, Linux with x86-64.
GOAMD64=v3 is new since Go 1.18,
I use v3 because I read about AMD64 version in Arch Linux rfcs. TLDR's newer computers are already x86-64-v3.

FROM gcr.io/distroless/base-debian11

COPY --from=builder /build/app /app

ENTRYPOINT ["/app"]
Enter fullscreen mode Exit fullscreen mode

Finally, I copy app to Distroless base image.

Discussion (5)

Collapse
andreidascalu profile image
Andrei Dascalu

In Dockerfile you should use ENV instead of run to set environment variables.

Also, why use multiple copy statements instead of just one and control what goes into the container with .dockerignore?

Also, if you copy go.mod/go.sum, you shouldn't need to copy vendor folder as well.

Collapse
haunguyentran profile image
sudo pacman -Syu Author

ENV is good choice I will look at it later.

The reason I have multi copy is I want to cache for each stage building. You can read it from here docker.com/blog/containerize-your-...

Why copy go.mod/go.sum is enough? I quite don't understand here.

Collapse
andreidascalu profile image
Andrei Dascalu

Well, caching go mod and go sum is the very definition of micro optimizations (caching the few bytes of go mod/sum in the off chance they haven't changed - makes sense for mod but less so for sum) which backfires for distributed images where each layers gets pulled via its own request.

Vendor folder is coonly used to store external dependencies in the local project. Generally it's not something to keep since basically what you define in go mod ends up in vendor. At build time you should pull dependencies based on go sum (unless you use vendor for a different purpose). Another reason to not copy it is when you build for a different architecture than your local. Your vendor will be pulled based on you local os/arch whereas if you want to specify different ones in Docker those dependencies will be rebuild anyway so that step becomes useless.

Thread Thread
haunguyentran profile image
sudo pacman -Syu Author • Edited on

Hmm in my use case, vendor is too big and network is kinda slow so I decide to just copy the vendor here. My purpose is that I don't want to download anything from the internet when I build (more time to wait :) )
My project include private repo. So to build with Go official image I need to set PROXY to workaround if go want to download private modules, I kinda hate it.

Also If I just edit some go file -> The step COPY . . (which copy vendor) is not cache so it will copy all the file in vendor again (slow too).

For case COPY go.mod, go.sum. I agree with you.

Collapse
cricketsamya profile image
Sameer

I agree with this! ENV should be used!