How to Shrink Docker Images
Smaller images pull faster, start faster, cost less to store and contain fewer packages with vulnerabilities. Going from 1.2 GB to under 200 MB is common.
1. Choose a smaller base image
node:22(Debian full) - large, has build tools.node:22-slim- Debian with the extras removed; good compatibility.node:22-alpine- very small; uses musl libc, which occasionally breaks native modules.- Distroless images - just the runtime, no shell or package manager. Smallest attack surface.
scratch- empty; perfect for static Go or Rust binaries.
2. Use multi-stage builds
FROM golang:1.25 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /app ./cmd/api
FROM gcr.io/distroless/static-debian12
COPY --from=build /app /app
USER nonroot
ENTRYPOINT ["/app"]
The Go toolchain (hundreds of MB) never reaches the final image, which ends up a few MB in size.
3. Clean up in the same layer
# Bad: the cache is deleted in a new layer but still stored in the previous one
RUN apt-get update && apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# Good
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
4. Ship only production dependencies
RUN npm ci --omit=dev
# Python: pip install --no-cache-dir -r requirements.txt (or uv sync --no-dev)
5. See where the space goes
docker images my-api
docker history my-api:latest # size of each layer
dive my-api:latest # interactive layer explorer (third-party tool)
6. Keep the build context lean
A good .dockerignore keeps node_modules, .git, test fixtures and build output out of COPY . ..
Was this article helpful?
Comments
No comments yet. Have a question or a tip? Be the first to share it.