Filters
How to search
  • Plain words find names, texts and examples.
  • "two words" finds the exact phrase.
  • --name or -x finds an option.
  • #word looks only inside commands.
  • @grep finds lessons and labs that use grep.
  • Filters: cat:network · in:labs · in:notes · level:rhcsa · ch:8 · root:yes. Put ! before a word to leave it out. OR between words means one of them.
  • ↑ ↓ to move · Enter to open · Esc to close · / to search

Chapter 5Name in the course: Image best practices

Build small images with multi-stage builds

Small images start faster, download faster and have fewer vulnerabilities. A multi-stage build compiles in a big "builder" image and copies only the result into a tiny runtime image. Add a non-root user, pinned bases and labels — and the image is production-ready.

  • 5 Lessons
  • 3 Commands

$ run as a normal user# run as root (or with sudo)

5.1

Split build and runtime

Each FROM starts a new stage. COPY --from=STAGE takes only the files you need.

Dockerfile (Go app)
FROM golang:1.23 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /out/app ./cmd/app
 
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/app /app
USER nonroot
EXPOSE 8080
ENTRYPOINT ["/app"]
docker build -t api:dev .
docker images api:dev
docker build --target build -t api:build .
Base imageSizeHas a shell / package manager
ubuntu, debian70 – 120 MBYes
*-slim30 – 80 MBYes, fewer packages
alpineabout 8 MBYes (musl libc, can differ)
UBI minimal / micro (Red Hat)30 – 100 MB / about 25 MBminimal: microdnf / micro: no
distroless2 – 20 MBNo — smallest attack surface

5.2

Run as a non-root user

Root in the container is root on the host if the container escapes. Use a fixed, non-root UID.

Dockerfile (user)
RUN groupadd -r app -g 1001 && useradd -r -u 1001 -g app app
USER 1001:1001
docker run --rm shop:dev id
uid=1001(app) gid=1001(app) groups=1001(app)

Remember

Many orchestrators (and OpenShift) run containers with a random UID. Make the app folder readable by the group 0, and do not write outside /tmp and mounted volumes.

5.3

Pin the base and rebuild often

Pin the base image (tag or digest) so builds are repeatable — and rebuild regularly so you get the security fixes.

Dockerfile (pinned)
FROM python:3.12.7-slim@sha256:DIGEST

At work

Let Dependabot or Renovate open PRs when a new base image digest appears, and rebuild every week even without code changes.

5.4

Add metadata

Standard OCI labels say where an image came from.

Dockerfile (labels)
LABEL org.opencontainers.image.source="https://github.com/s0sta/shop" \
      org.opencontainers.image.revision="${GIT_SHA}" \
      org.opencontainers.image.version="${VERSION}"

5.5

Check an image before you ship it

Look at size, layers, user and vulnerabilities.

docker image inspect shop:dev --format '{{.Config.User}} {{.Size}}'
docker history --no-trunc shop:dev | head
trivy image --severity HIGH,CRITICAL shop:dev
hadolint Dockerfile

hadolint lints Dockerfiles: missing pins, apt-get without cleanup, ADD instead of COPY, root user.

All commands in this chapter