Docker cookbook · Beginner
Ship a Go service in a tiny distroless image
Compile a static Go binary and run it in an image with no shell or package manager.
Approach
Build with CGO_ENABLED=0 for a static binary, then copy it into gcr.io/distroless/static with the
nonroot user. Cache Go modules and the build cache with BuildKit mounts.
Solution
# syntax=docker/dockerfile:1
FROM golang:1.26-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod go mod download
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/server ./cmd/server
FROM gcr.io/distroless/static-debian13:nonroot
COPY --from=build /out/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
Pitfalls
- Forgetting
CGO_ENABLED=0: the binary needs libc and fails in a static image. - Needing CA certificates or time zones: distroless static includes them;
scratchdoesn't. - Expecting to
docker exec shinto it for debugging; use a debug container instead.
Variations
FROM scratchwith explicitca-certificatesandtzdatacopies for the very smallest image.- Cross-compile for arm64 with
GOARCHinstead of emulation.
Further reading: Distroless images
Reference: https://github.com/GoogleContainerTools/distroless
More Docker recipes
- Add a healthcheck to an HTTP service
- Build a layered image for a Spring Boot app
- Build a production image for a Node.js service
- Build a production image for a Python web app
- Build, cache and push images in GitHub Actions
- Build one image tag for amd64 and arm64
- Configure nginx from environment variables
- Develop with Compose and live reload