Build a Go app
Go is the cleanest case for multi-stage builds. A Go program compiles to a single static binary, so the runtime image needs nothing but that file. Build on the go image, copy the binary onto static, and the final image is little more than your program: no toolchain, no shell, no package manager.
This builds on ghcr.io/quenchworks/images/go:1.25 and ghcr.io/quenchworks/images/static. The static image is the one image in the catalog tagged :latest, because it carries no language version of its own.
The two-stage Dockerfile
# Build stage: compile a fully static binary.FROM ghcr.io/quenchworks/images/go:1.25 AS buildWORKDIR /src
# CGO off makes the binary static so it runs on the static base with no libc.# The root filesystem is read-only, so point the build caches at /tmp.ENV CGO_ENABLED=0 \ GOOS=linux \ GOCACHE=/tmp/gocache \ GOMODCACHE=/tmp/gomodcache
# Download modules first so the layer caches when only code changes.COPY go.mod go.sum ./RUN ["go", "mod", "download"]COPY . .RUN ["go", "build", "-trimpath", "-ldflags=-s -w", "-o", "/out/app", "./cmd/app"]
# Runtime stage: just the binary on the tiny static base.FROM ghcr.io/quenchworks/images/staticCOPY --from=build /out/app /appUSER 1001EXPOSE 8080ENTRYPOINT ["/app"]Why this stays tiny
- Build. The
goimage has the full toolchain.CGO_ENABLED=0produces a static binary with no dynamic libc dependency, which is exactly what thestaticbase needs. The caches go to/tmpbecause the build runs on a read-only root filesystem. - Runtime.
staticis a minimal base with a nonroot user and CA certificates, and not much else. Copy the one binary in, set the entrypoint, and you are done. There is no compiler and no shell to leave behind.
Next
- The same
staticbase works for any language that compiles to a static binary, Rust included; build withghcr.io/quenchworks/images/rustand copy the binary over. - Pin by digest so each build runs exactly the base that was scanned and signed.