CKSKubernetes 1.35

Lessons

Minimising the base image footprint

updated 2026-10-22 by seed

Why it is on the exam

A smaller image has fewer packages, so fewer CVEs, so less to exploit. It also
pulls faster and has no shell for an attacker to land in.

The four changes, in order of impact

1. A smaller base

FROM node:22                 # ~1.1 GB, Debian, shell, package manager
FROM node:22-alpine          # ~140 MB
FROM gcr.io/distroless/nodejs22-debian12   # ~80 MB, no shell, no package manager

Distroless has no /bin/sh, no apt, no shell — kubectl exec -- sh fails,
which is the point and also a debugging cost you should mention.

2. Multi-stage build

# build stage: compilers, headers, caches — all thrown away
FROM golang:1.23-alpine AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app ./cmd/app

# runtime stage: only the binary and its CA bundle
FROM alpine:3.20
RUN apk add --no-cache ca-certificates
COPY --from=build /out/app /app
USER 65532:65532
ENTRYPOINT ["/app"]

The final image contains no toolchain. That is the entire trick.

3. Fewer layers, no cache in the image

# instead of three layers, one — and clean up in the same step
RUN apt-get update && apt-get install -y --no-install-recommends curl \
 && rm -rf /var/lib/apt/lists/*

rm -rf in a later RUN does not shrink the image: layers are additive, the
bytes stay in the earlier layer.

4. Pin by digest

FROM alpine:3.20@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b

A tag is mutable — latest today is not latest tomorrow, and a tag can be
repointed at a malicious image. A digest cannot.

The Dockerfile task, verbatim-ish

"Fix this Dockerfile: it uses latest, runs as root, copies the whole build
context, and installs packages without cleaning up."

FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY src/ ./src/

FROM node:22-alpine
RUN addgroup -S app && adduser -S -G app app
WORKDIR /app
COPY --from=build /app /app
USER app
EXPOSE 3000
CMD ["node", "src/server.js"]

Then the .dockerignore:

.git
node_modules
*.log
Dockerfile

Proving the size

docker images app --format '{{.Repository}}:{{.Tag}} {{.Size}}'
dive app:1.0        # layer-by-layer, shows what is wasting space
trivy image app:1.0 # what is still vulnerable

In Kubernetes, not Docker

The exam may not give you a Docker daemon at all. The same points can be made
with the Pod spec: imagePullPolicy: IfNotPresent with a digest, readOnlyRootFilesystem,
non-root UID, and no shell in the image. If there is no builder, the task is
about choosing and pinning, not building.