Reputation: 111
In short, my current use-case involves dynamically creating a Golang plugin inside a Docker container. The compilation involves some new input from the user (which is why it is not compiled beforehand), but the dependencies are static, and won't change.
Currently, the full compilation is done from scratch inside the Docker container (though go mod download
is used to reduce the time by a bit). I noticed that the go build
command ends up compiling a lot of the dependencies, which adds a non-trivial amount of time for the plugin compilation, which affects the usability of my application.
Is there a Go supported method or command to read through the go.mod
file and populate the GOCACHE
directory? With such a command, I would run it in my Dockerfile
itself, causing the Docker image to contain the cache with all the compiled build dependencies.
What I've tried:
go mod download
: This only downloads the dependencies; it does not compile them.main.go
that imports all the dependencies, and run go build
within my Dockerfile
to populate the cache. As mentioned, this does solve my problem, but it feels like a bit of a hack. Additionally, if the dependencies change in the future, it requires someone to change this as well, which isn't ideal.Upvotes: 10
Views: 1468
Reputation: 1
I do have this working with a temporary workaround: I created a barebones main.go that imports all the dependencies, and run go build within my Dockerfile to populate the cache. As mentioned, this does solve my problem, but it feels like a bit of a hack.
^^ I think your "temporary workaround" is the simplest way to cache compiled 3rd party + transitive dependencies. Below is my recommended implementation:
Steps:
dependencies.go
file, preferably at the root directory, like mine below, which includes packages from your go.mod
file:dependencies.go
// depedencies.go
package main
import (
_ "runtime/pprof"
_ "testing"
_ "github.com/Knetic/govaluate"
_ "github.com/alecthomas/chroma/v2"
_ "github.com/cloudflare/cfssl/helpers"
_ "github.com/go-faker/faker/v4"
_ "github.com/go-resty/resty/v2"
_ "github.com/go-rod/rod"
_ "github.com/google/certificate-transparency-go"
_ "github.com/hashicorp/go-version"
_ "github.com/jaytaylor/html2text"
_ "github.com/kataras/jwt"
_ "github.com/kljensen/snowball"
_ "github.com/kljensen/snowball/english"
_ "github.com/schollz/progressbar/v3"
_ "github.com/shirou/gopsutil/v3/mem"
_ "github.com/spf13/cobra"
_ "github.com/spf13/viper"
_ "github.com/tklauser/go-sysconf"
_ "github.com/yuin/goldmark/text"
_ "github.com/zcalusic/sysinfo"
_ "golang.org/x/crypto/openpgp/packet"
_ "golang.org/x/crypto/scrypt"
_ "golang.org/x/net/context"
_ "golang.org/x/text/cases"
_ "golang.org/x/text/encoding/korean"
_ "golang.org/x/text/encoding/traditionalchinese"
_ "golang.org/x/text/language"
_ "golang.org/x/time/rate"
_ "gopkg.in/yaml.v2"
)
func main() {
}
Dockerfile
like below, in the root directory.Dockerfile
FROM --platform=linux/amd64 golang:1.22.1-bullseye as golang-image-builder
WORKDIR /src
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY dependencies.go .
RUN go build all
RUN rm dependencies.go
COPY config ./config
COPY cmd ./cmd
COPY internal ./internal
ENV VERSION="1.2.3"
RUN go build -v -o executable-name -ldflags="-s -w -X main.version=$VERSION" ./cmd/executable-name
ENTRYPOINT ["./executable-name"]
RUN go build -v -o executable-name -ldflags="-s -w -X main.version=$VERSION" ./cmd/executable-name
, which will likely tell of you names of extra packages you should add to your dependencies.go
fileUpvotes: 0
Reputation: 340
Since the issues (1, 2) in the golang repository are still open, all that we can do is "hacking", I think. So, we can do something like that for the dependencies caching and pre-compilation as a separate docker layer:
FROM golang:1.19-buster as builder
COPY ./go.* /src/
WORKDIR /src
# burn the modules cache
RUN set -x \
# cache go dependencies
&& go mod download \
# pre-compile common dependencies
&& mkdir /tmp/gobin \
&& for p in $(go list -m -f '{{if and (not .Indirect) (not .Main)}}{{.Path}}/...@{{.Version}}{{end}}' all); do \
GOBIN=/tmp/gobin go install $p; \
done \
&& rm -r /tmp/gobin
COPY . /src
RUN go build ...
Without this trick, the building (docker buildx build --platform linux/amd64,linux/arm64 ...
) takes about 9 minutes, with it ~6 minutes (profit 30%). But the pre-compilation step becomes longer by ~40%.
Upvotes: 3