Choosing a base image
| Kind | Example | When |
|---|---|---|
| Full distro | ubuntu:24.04, debian:12 |
Many system tools needed |
| Slim | python:3.12-slim, node:22-slim |
A good default for most apps |
| Alpine | alpine:3.20 |
Very small, uses musl (may behave differently from glibc) |
| Distroless | gcr.io/distroless/static |
No shell, no package manager |
| Scratch | scratch |
Static binaries such as Go |
Use images only from trusted sources: Docker Official Images, Verified Publishers or a vetted internal registry. A stranger's image on Docker Hub may hide a miner or a backdoor.
Pinning for reproducible builds
Tags such as node:22 or latest can point at different content every week. A digest is a content hash and never changes:
FROM node:22-slim@sha256:EXAMPLE # the tag for humans, the digest for machines
Combine this with Renovate or Dependabot so digests are updated through checked pull requests instead of drifting silently.
Multi-stage builds
FROM golang:1.23 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /app ./cmd/server
FROM gcr.io/distroless/static:nonroot
COPY --from=build /app /app
USER nonroot
ENTRYPOINT ["/app"]
The build stage has the compiler and tools; the final stage has only the binary. The image is smaller with fewer CVEs, and an attacker who gets into the container finds no shell or curl to use.
flowchart LR A["golang:1.23<br/>~800 MB, many packages"] -- "COPY --from=build" --> B["distroless/static<br/>a few MB, binary only"]
Other habits
- A non-root
USER; many images ship users such asnodeornonroot. - No extra packages:
apt-get install --no-install-recommendsand clean the apt cache in the sameRUN. - A
.dockerignoreexcluding.git,.env,node_modulesand test files. - Rebuild regularly (for example weekly) to pick up base image fixes even when the code has not changed.
- Check with
trivy image, plusdocker scoutorhadolintfor the Dockerfile.
Traps: using latest in production; never rebuilding after the first build; copying the whole root folder, secrets included, into the image; assuming Alpine always has fewer issues without scanning.