Packaging a Go HTTP server in Docker starts with a single-stage image that includes the compiler; that is convenient for development but often hundreds of megabytes on disk. A golang Docker multi-stage build compiles in one stage, then copies the server binary into a slim runtime such as Alpine, which typically shrinks the artifact to tens of megabytes depending on base tags and cgo. For background on images and containers, see Docker containers and keeping containers running.
Tested with Go 1.24 and Docker on Linux; exact image sizes depend on registry tags and cache.
Prerequisites
- Go installed locally to iterate on
main.go - Docker installed (install Docker on Linux)
- An editor such as VS Code
Minimal HTTP app
Create a directory, add main.go, and run go mod init (custom modules; if go.mod is missing you may see go.mod not found).
package main
import (
"fmt"
"log"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello from Go Docker multistage")
}
func main() {
http.HandleFunc("/", handler)
fmt.Println("server running at 0.0.0.0:8080")
log.Fatal(http.ListenAndServe("0.0.0.0:8080", nil))
}Run with go run . and open port 8080 on the host to confirm the handler text.
Single-stage Dockerfile (large)
FROM golang:1.24-alpine
WORKDIR /src/app
COPY go.mod main.go ./
RUN go build -o /server
CMD ["/server"]docker build -t go-single . then docker images shows a large image because the compiler and module layers remain in the final layer stack. Docker build without cache is useful when you need a clean rebuild.
Multi-stage Dockerfile (small runtime)
FROM golang:1.24-alpine AS builder
WORKDIR /src/app
COPY go.mod main.go ./
RUN CGO_ENABLED=0 go build -o server
FROM alpine:3.20
WORKDIR /root/
COPY --from=builder /src/app/server ./server
EXPOSE 8080
CMD ["./server"]The first stage produces server; the second stage is a minimal OS that only receives the binary. CGO_ENABLED=0 avoids libc linkage when you target musl/glibc mix—adjust if you need cgo.
Build and run:
docker build -t go-multistage .
docker run --rm -p 8080:8080 go-multistageMap host port to container port the same way as other published services (open ports on Linux).
Compare sizes with docker images | grep go-multistage; the final tag should be far smaller than a single FROM golang stage on the same machine.
Summary
A golang Docker multi-stage build separates compile from run: the builder stage has Go tooling; the runtime stage only needs the static-ish binary and a tiny base. That cuts download size and attack surface compared to shipping the full SDK in production.

