Intro
According to the Go command documentation:
“The go command caches build outputs for reuse in future builds.”
Locally, that’s awesome: first go build is slow, next ones are much faster thanks to the cache.
Inside Docker, though, each build runs in a fresh container - so the Go build cache disappears unless you explicitly persist it. That’s what we’ll fix here. 🚀
Baseline: Dockerfile without Go cache
Here’s a very typical multi-stage Dockerfile:
FROM golang:1.25-trixie as builder
WORKDIR /app
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
RUN go build -o app
FROM ubuntu:24.04
RUN mkdir /app
WORKDIR /app
COPY --from=builder /app/app .
ENTRYPOINT ["./app"]
What it does:
Builder stage
- Copies
go.mod/go.sum - Runs
go mod download - Copies the rest of the source
- Runs
go build -o app
- Copies
Runtime stage
- Uses a clean
ubuntu:24.04image - Copies the built binary
- Uses a clean
This already uses Docker layer cache for steps like go mod download, as long as go.mod and go.sum don’t change.
But there’s one big problem:
- Every
RUN go buildhappens in a fresh container. - The Go build cache (
GOCACHE) is not persisted between builds. - So each Docker build recompiles everything, even if you only changed one file.
In my simple “Hello, world!” example, the image build took around 26s.
Docker BuildKit cache mounts
Docker BuildKit supports cache mounts: persistent directories that survive across builds and can be reused by later runs of RUN.
From the docs:
Cache mounts let you specify a persistent package cache to be used during builds.
Syntax (inside RUN):
RUN --mount=type=cache,target=/path/to/cache <your command>
We’ll use this to persist Go’s build cache directory.
Optimized Dockerfile with Go cache
Here’s the improved version:
FROM golang:1.25-trixie as builder
WORKDIR /app
# 1. Dependencies first (better Docker layer caching)
COPY go.mod .
COPY go.sum .
RUN go mod download
# 2. Copy the rest of the source
COPY . .
# 3. Explicit Go build cache location
ENV GOCACHE=/root/.cache/go-build
# 4. Use Docker cache mount for Go build cache
RUN --mount=type=cache,target="/root/.cache/go-build" \
go build -o app
FROM ubuntu:24.04
RUN mkdir /app
WORKDIR /app
COPY --from=builder /app/app .
ENTRYPOINT ["./app"]
Key parts:
ENV GOCACHE=/root/.cache/go-buildTells Go where to keep its build cache.--mount=type=cache,target="/root/.cache/go-build"Tells Docker/BuildKit to persist that directory across builds.
Result: On the same “Hello, world!” project, rebuild time dropped from ~26s to ~1.2s. That’s roughly a 20x speedup.
Why this works
- Go compiles packages and writes artifacts into
GOCACHE. - Normally, this cache is lost at the end of the Docker layer.
- BuildKit cache mounts keep
/root/.cache/go-buildoutside the ephemeral container filesystem. - Next time you run
docker build, Go finds its previous compiled packages and only rebuilds what changed.
This scales especially well for:
- Large Go monoliths
- Many internal modules
- Heavy codegen or expensive compilation steps
Requirements / notes
You need BuildKit enabled (modern Docker does this by default).
Cache mounts only affect build time, not the final runtime image size.
This pattern also plays nicely with:
CGO_ENABLED=0-ldflagsfor versioningGOOS/GOARCHfor cross-compilation
Links
That’s all.