The problem that I wanted to solve was a long docker build time for images. I had a Go application and each time I’ve changed the source code and wanted to rebuild the image it took so much time. The issue was that the layers were wrongly defined and there was no support for Go caching mechanisms.

To reduce the time need for a build I’ve added:

  • cache for go mod download
  • cache for go build
  • remove COPY and mount volume with sources instead
  • reduce data copied to docker build context

I’ve also used the Dockerfile frontend experimental syntaxes as was describe nicely in this blog post series.

Final Dockerfile:

# syntax = docker/dockerfile:1-experimental

FROM golang:1.15.2-alpine as builder

ARG COMPONENT
ARG SOURCE_PATH="./cmd/$COMPONENT/main.go"

WORKDIR /src

# Copy the go modules files before copying and building source, so that we don't need to re-download them
# and source code changes don't invalidate our dependency layer.
COPY go.mod go.sum ./
# Use frontend syntax to cache dependencies.
RUN --mount=type=cache,target=/go/pkg/mod \
    go mod download

# Replace `COPY . .` with `--mount=target=.` to speed up as we do not need them to persist in the final image.
# Use frontend syntax to cache go build.
# Mount the cached dependencies from the previous layer, not doing it, cause that Go build will download them once again.
RUN --mount=target=. \
    --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 GOARCH=amd64 go build -ldflags "-s -w" -o /bin/$COMPONENT $SOURCE_PATH

FROM scratch
ARG COMPONENT

COPY --from=builder /bin/$COMPONENT /app

LABEL source=[email protected]:mszostok/til.git
LABEL app=$COMPONENT

CMD ["/app"]

Now to build an image, run:

# enable the BuildKit builder in the Docker CLI.
export DOCKER_BUILDKIT=1
# build image
docker build --build-arg COMPONENT=agent -t mszostok/agent:0.0.1 .