How to Shrink Docker Images from Gigabytes to Kilobytes with Multi‑Stage Builds
This article explains why beginner Docker images can exceed 1 GB, then walks through multi‑stage builds, static versus dynamic linking, language‑specific tricks, and the use of minimal base images like scratch to reduce image size by over 99 % while preserving functionality.
Overview
Docker images built directly from language runtime images (e.g., gcc, golang) often exceed 1 GB even for a tiny executable. The size is dominated by the build toolchain and runtime libraries, not by the compiled binary itself. The article demonstrates how to shrink images using multi‑stage builds, classic base images, static linking, and minimal scratch images.
Multi‑stage Build Example
Compile the program in a heavyweight stage and copy only the resulting binary into a lightweight final stage.
# hello.c
int main() {
puts("Hello, world!");
return 0;
} # Dockerfile (single stage – large image)
FROM gcc
COPY hello.c .
RUN gcc -o hello hello.c
CMD ["./hello"]Result: image > 1 GB.
# Dockerfile (multi‑stage – reduced image)
FROM gcc AS builder
COPY hello.c .
RUN gcc -o hello hello.c
FROM ubuntu
COPY --from=builder hello .
CMD ["./hello"]Result: ≈64 MB (≈95 % reduction).
Choosing Classic Base Images
For the build stage use well‑known distributions such as CentOS, Debian, Fedora or Ubuntu. Alpine is avoided initially because its musl libc can cause compatibility issues with binaries built against glibc.
Using COPY --from with Absolute Paths
If the first stage changes WORKDIR, relative paths in COPY --from may break. Define a stable WORKDIR in the builder and copy using an absolute path:
FROM golang
WORKDIR /src
COPY hello.go .
RUN go build -o hello hello.go
FROM ubuntu
COPY --from=0 /src/hello .
CMD ["./hello"]This reduces a Go build from ~800 MB to ≈66 MB .
All images are tagged with the same repository name (e.g., hello:gcc , hello:ubuntu ) so that docker images hello lists only the variants.
Using scratch for Minimal Images
scratchis a virtual empty base. The final image contains only the copied binary and its required libraries.
# Dockerfile targeting scratch (static binary required)
FROM golang AS builder
COPY hello.go .
RUN go build -ldflags "-s -w" -o hello hello.go
FROM scratch
COPY --from=builder /go/hello .
CMD ["./hello"]Resulting image size matches the binary size (e.g., 2 MB for the Go example).
Limitations of scratch
Missing shell : Commands must use JSON syntax ( CMD ["./hello"]) because /bin/sh does not exist.
Missing debugging tools : Utilities such as ls, ps, ping are unavailable; docker exec cannot be used. Use docker cp or network‑namespace tricks, or choose a lightweight base like busybox or alpine for easier debugging.
Missing C library : Most binaries depend on dynamic libraries (e.g., libc.so.6). Without them the container fails to start.
Resolving the libc Dependency
Static linking : Compile with -static (GCC) or appropriate linker flags (Go). Example: gcc -static -o hello hello.c Resulting binary ~760 KB; image size ≈940 KB when used with scratch.
Copy required libraries : Identify dependencies with ldd and copy them into the image. Example:
ldd hello
# output shows libc.so.6, ld-linux-x86-64.so.2, etc.Copy each listed file into the final stage. This approach becomes fragile for programs with many indirect dependencies (e.g., DNS resolution requires /lib64/libnss_*).
Use a small glibc‑based image : busybox:glibc (~5 MB) provides glibc and basic debugging tools, offering a practical compromise between size and usability.
Size Comparison Summary
Original single‑stage gcc build: 1.14 GB
Multi‑stage with ubuntu: 64.2 MB alpine + static glibc: 6.5 MB alpine + dynamic libs: 5.6 MB scratch + static glibc: 940 KB scratch + static musl libc: 94 KB
Overall reduction reaches 99.99 % . For everyday development the article recommends avoiding scratch because debugging is cumbersome; a minimal busybox or alpine base is usually a better trade‑off.
Liangxu Linux
Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
