Keep the build environment out of runtime
An image containing a compiler, test tools and the final application carries more than the runtime needs. Multi-stage builds separate those concerns and copy selected artifacts into the final stage. A compiled Go application demonstrates the idea clearly: the first stage produces an executable and the second runs it. Reducing image size can simplify transfer and inspection, but it does not independently establish security or improve application latency.
The example builds a minimal HTTP service entirely within a Dockerfile. It has no external Go dependencies and compiles a single source file. Disabling CGO makes this particular binary suitable for scratch. That image is not a general operating environment with a shell and troubleshooting utilities, which affects operations. The cover comes from Docker’s official web-application image article; our Go implementation is an independent teaching example of the build-stage pattern.
A small image is not the only goal
In a real project, copy dependency manifests before other source files so small code changes do not invalidate dependency installation. A .dockerignore should exclude local directories, temporary output and secrets. Do not introduce credentials through ARG or COPY; use the build system’s secret mechanisms where appropriate. Record base versions and, when required, digests for reproducible releases. A floating instructional tag is convenient, but production provenance needs the exact versions actually built.
Test a non-root user and restricted filesystem access from the start. Our application listens on port 8080 rather than a privileged port. Programs making outbound HTTPS requests or using time-zone files may need additional runtime assets that scratch does not provide. Discover these requirements through tests instead of adding packages indiscriminately. Graceful shutdown, server timeouts, health checks and error logging remain work before production; the demo is intentionally a minimal build illustration.
Measure image size, cold build time, rebuild time after a source change and startup latency separately. The smallest image may not be the best operational choice for a team that needs debugging tools or additional library compatibility. Inspect the final artifact and its build dependencies. If publishing multiple architectures, test each one. A successful developer-machine build does not demonstrate equivalent behavior on an ARM server or in a constrained runtime.
Code example and verification
This educational example demonstrates the implementation path. Check the stated runtime and prerequisites in a test environment; the notes explain what remains before production use.
# syntax=docker/dockerfile:1
FROM golang:1.26 AS build
WORKDIR /src
COPY <<EOF main.go
package main
import ("fmt"; "log"; "net/http")
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "ready")
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
EOF
RUN CGO_ENABLED=0 go build -trimpath -o /out/server main.go
FROM scratch
COPY --from=build /out/server /server
USER 65532:65532
EXPOSE 8080
ENTRYPOINT ["/server"]Save as Dockerfile. Build with docker build -t liyan-go-demo . and run docker run --rm -p 127.0.0.1:8080:8080 liyan-go-demo. Request http://127.0.0.1:8080 and expect ready. Add timeouts and graceful shutdown before production. The 1.26 base is the stated demo version; select reviewed patch versions and digests for your release.
Move from a successful build to an operable service
Before release, run with the target user, memory limits and network configuration and make a real HTTP request. Record the previous rollback artifact, new digest and verification result. The release process should be traceable and repeatable. Shared caching, SBOM generation and signing can follow. Multi-stage builds become valuable when the team understands what was consumed during build, what reached the final artifact and what that artifact requires to operate.
Implementation checklist
- Use Docker with BuildKit and network access for the build image.
- Exclude local configuration and secrets with .dockerignore.
- Test non-root execution and target resource limits.
- Record size, digest, rollback artifact and HTTP verification.
Practical explanations and recommendations are Liyan Knowledge editorial analysis.Sources: Docker — Multi-stage builds · Docker — Building best practices · Docker — Build secrets
This Liyan Knowledge article is an editorial synthesis based on the original source.View original source





