The issue opens pre-filled with this page title and URL.
Supply chain security accounts for 20% of the CKS and covers reducing the image footprint, understanding the supply chain, securing artifacts and the static analysis of workloads and images. This guide focuses on five concrete practices: minimal images, vulnerability scanning, SBOM, image signing and admission control.
These five practices cover the foundation, but they are only worth something once inserted into a wider chain: continuous integration, artifact storage and admission controls.
Supply chain security is the broadest domain of the CKS, with 20% of the exam.
One point of method matters for studying well: the official curriculum describes
skills, reducing the image footprint, securing artifacts, analysing
statically, controlling admission, and imposes no tool. This guide therefore
follows that structure by objective, and the tools appearing in it are
interchangeable means.
Prerequisites
A working Kubernetes cluster with cluster-admin access
Docker and container basics (building images)
kubectl configured
Security notions (CVE, vulnerabilities)
What is an image's supply chain?
The supply chain of a container image covers its whole lifecycle before deployment:
An attack on the supply chain compromises any link: a malicious dependency, a hijacked build, an image modified in transit, or an unauthenticated registry.
Golden rule: never trust an image just because it "works". An image that works can still contain critical CVEs or have been tampered with.
1. Minimising the base image footprint
This is the first step because it is the only one that removes risk
instead of adding detection. Every measure that follows, signing,
analysis, SBOM, deals with what the image contains: reducing that content
reduces just as much what they have to cover.
The gain is immediate: a package absent from the image generates no
vulnerability alert, needs no update, and gives no lever to anyone who
manages to run code in the container.
Why lightweight images reduce the attack surface
An image shipping apt, bash, curl and system tools offers an attacker as many levers if they manage to run inside the container. A distroless image contains only the runtime and your binary.
Image type
Size
Attack surface
Use case
ubuntu:22.04
~70 MB
High (shell, apt, utils)
Development
alpine:3.19
~7 MB
Moderate (BusyBox shell)
General use
gcr.io/distroless/static
~2 MB
Minimal (no shell)
Go, static binaries
gcr.io/distroless/base
~20 MB
Very low (glibc only)
C, C++, Java
scratch
0 MB
Minimal on the base image side (security still depends on the shipped binary)
Fully static binaries
Swipe to see more
Multi-stage build: compiling without shipping the tools
A multi-stage build lets you compile in a full "build" image and copy only the final binary into a minimal image:
Dockerfile
# Stage 1: compilation (full image)
FROM golang:1.23@sha256:60deed95d3888cc5e4d9ff8a10c54e5edc008c6ae3fba6187be6fb592e19e8c0 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o myapp .
# Stage 2: minimal final image (distroless)
FROM gcr.io/distroless/static-debian12:nonroot@sha256:afa5c872c891853ca7fcf1f12c3edb23f7eeef36189728842dd51042ff57f7ab
COPY --from=builder /app/myapp /myapp
USER nonroot:nonroot
ENTRYPOINT ["/myapp"]
Image good practices
These four rules address the same question: will the image you deploy
today be identical to the one you tested yesterday? A tag is a
mutable pointer on the registry side, reassignable without anything changing
in your manifest; a digest is the hash of the content, and it cannot lie.
Hence the hierarchy: latest guarantees nothing, a version tag guarantees an
intent, a digest guarantees the content.
Freeze the version: nginx:1.25.3 rather than nginx:latest, an update can introduce CVEs
Add the digest to guarantee immutability, without removing the tag: nginx:1.25.3@sha256:abc123.... The tag stays readable for humans, the digest commits the content
Do not install debuggers in production images (gdb, strace, tcpdump)
Clean the cache in the same RUN: apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# Recommended: tag AND digest, readable and immutable
FROM nginx:1.31.3@sha256:8541484afbc9c8a5a8a99b379568ebbc957f658583ec9448fc43104229c03cf8
# ❌ to ban: latest is not reproducible
FROM nginx:latest
The # ❌ marker is not decorative. The site's pinning tooling
rewrites image references, and only stops at this marker: without it, a
latestcounter-example gets "fixed" into a pinned image, and the
demonstration disappears silently.
2. Scanning for vulnerabilities
Before anything else: scan your images to detect known CVEs (Common Vulnerabilities and Exposures).
The March 2026 Trivy incident, and what it changes here
In March 2026, the Trivy ecosystem suffered a multi-wave
compromise: publication of a malicioustrivy v0.69.4, compromise of the
trivy-actiontags, exposure of setup-trivy, then malicious Docker Hub
images v0.69.5 and v0.69.6. Aqua recommended treating as compromised
every secret reachable by the affected pipelines.
The episode illustrates exactly what this chapter teaches: a supply chain
security tool is part of that chain, and gets compromised like any other
link. It also justifies the position taken here. The Trivy commands
stay in this guide, because they help to read existing pipelines and they
are everywhere in exam preparation material; but the recommended path for a
new deployment goes through another scanner.
Scanning with Grype, my main choice
I favour it for its integration with Syft, for its prioritisation of real risk, backed by EPSS, KEV and OpenVEX, and for its ability to scan images, filesystems and SBOMs alike.
Terminal window
# Installation from the verified official binary (SHA256 checksum)
# Scan with a severity threshold (fail if CRITICAL found)
grypenginx:1.25.3--fail-oncritical
# JSON format for CI/CD
grypenginx:1.25.3-ojson>results.json
Scanning with Trivy, ecosystem culture
You will meet these commands in almost every existing pipeline and CKS
preparation material, which is enough to justify knowing them. Two options
deserve an explanation. --ignore-unfixedhides CVEs without a published
fix: useful not to block a build on a problem nobody can solve,
dangerous if you enable it without tracking those vulnerabilities elsewhere.
--exit-code 1, for its part, turns the scan into a guardrail: without this
option, Trivy prints the CVEs and the job stays green.
A useful precision if you study from several sources: you will see
Trivy almost everywhere, and might conclude it is part of the
curriculum. It is not. The official curriculum describes the
skill, analysing an image and deciding on the result, without ever naming
a tool. Knowing how to read Trivy output stays useful; making it the only
scanner you know would be a study mistake.
The limit to keep in mind before moving on: an image without known CVEs is
not a trustworthy image. It may have been tampered with, come from a
compromised source, or carry malicious code no database references, the Trivy
incident being the direct demonstration. The scan answers a precise question,
"which known flaws does this image contain", and it replaces neither signing,
which establishes integrity, nor provenance, which establishes origin, nor
the admission policy, which decides what enters the cluster.
An SBOM (Software Bill of Materials) is the exhaustive inventory of an image's components: system packages, libraries, versions and licences. It is the ingredient list of your software.
Terminal window
# Generate an SBOM with Syft
syftnginx:1.25.3-ocyclonedx-json>nginx-sbom.json
# Use the SBOM to scan with Grype
grypesbom:nginx-sbom.json
# Generate and scan in one command
syftnginx:1.25.3|grype
See the full SBOM guide for the SPDX and CycloneDX formats and CI/CD integration.
4. Signing and verifying images with Cosign
Image signing guarantees authenticity (the image really comes from the expected build) and integrity (it has not been modified). Sigstore's Cosign is the standard tool.
Signing an image
A supply chain guide that installed its own tool without verification
would contradict itself: the block below pins a precise version and checks
the SHA256 checksum published by the project before making the binary
executable. Sigstore publishes this file as cosign_checksums.txt in
every release. The cosign generate-key-pair command then asks for a
passphrase: it protects cosign.key, the one thing never to let out of
your vault.
Terminal window
# Cosign installation: pinned version + checksum verification
A signature that verifies proves nothing on its own: you need to see the
verification fail when it should fail. The three cases below were run
on a local registry with cosign 3.1.3, and they return three
different exit codes.
With the right key, cosign details what it checked:
Output
Verification for localhost:5555/lab-signe:1.0.0 --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- Existence of the claims in the transparency log was verified offline
- The signatures were verified against the specified public key
Situation
Exit code
Message
Valid signature
0
the detail of the checks above
Wrong public key
1
no matching attestations: failed to verify log inclusion
Unsigned image
10
no signatures found
Swipe to see more
The distinction between 1 and 10 can be put to use: an unsigned image
signals an omission in the build chain, a signature that does not match
signals an identity problem, and these two incidents do not call for the same
response.
Where the signature lives
It is not in the image: it is a separate artifact, pushed to the same repository under a tag derived from the digest. After signing, the repository therefore holds two tags:
Two practical consequences. Copying an image from one registry to
another does not carry its signature, unless you also copy that artifact:
it is the most frequent cause of a failing verification after a registry
migration. And since the tag is derived from the digest, re-signing a
rebuilt image produces a new artifact, the old one staying in place.
Verification in Kubernetes with ImagePolicyWebhook
ImagePolicyWebhook is a historical admission controller, disabled by default, which delegates the authorisation decision to an external backend. It is still documented, and useful to understand image control mechanisms; but on recent clusters, you rather meet generic admission webhooks (ValidatingAdmissionWebhook) or policy engines such as Kyverno.
An architecture point that changes how to assess this mechanism:
ImagePolicyWebhook decides nothing by itself. It forwards the request
to an external service reachable over TLS, which allows or refuses. The
real security of the setup is therefore that of this backend, its
availability and the rules it applies, not that of the Kubernetes
controller, which merely relays.
The setup takes two files. First the kube-apiserver flags, to add in
/etc/kubernetes/manifests/kube-apiserver.yaml on every control plane
node: the first enables the plugin, the second says where to read its
configuration. A mistake here prevents the apiserver from restarting: keep
a copy of the manifest before modifying it.
defaultAllow: false# Block if the webhook is unreachable
A word on the signing mode, since two exist and the second is not
always presented. Key-based signing, the one this guide details, has the
advantage of being explicit: you see what you generate and what you
verify. It does leave you a long-lived private key to protect and
rotate, which is precisely the stuff of supply chain incidents.
Keyless signing removes that problem by relying on an OIDC identity,
that of your GitHub Actions or GitLab CI run, and on an ephemeral
certificate. That is what to aim for in a modern chain. Keep key-based
signing to understand the mechanisms, and for isolated environments where
no OIDC provider is reachable. The Cosign guide
details both.
5. Limiting the allowed registries
On top of signing, you can restrict Kubernetes to only accept images coming from approved registries. That is the modern, recommended way, through a policy engine you install, Kyverno or Gatekeeper, more flexible than ImagePolicyWebhook. Neither is part of Kubernetes. What is native, however, are CEL admission policies: ValidatingAdmissionPolicy, stable since 1.30, and MutatingAdmissionPolicy, stable and enabled by default since 1.36.
With Kyverno (recommended)
The policy below uses the policies.kyverno.io/v1 API, which appeared in
Kyverno 1.17 and has been stable since 1.18. Two fields carry all the
behaviour: validationActions: [Deny]refuses the creation instead of
merely reporting, and the CEL expression walks every container of the
Pod with all(...), which also blocks a Pod in which a single container
comes from elsewhere. Note that matchConstraints only targets Pods here: a
Deployment will pass, then its Pod will be refused, with an error message
showing in the ReplicaSet and not in the Deployment.
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: restrict-registries
spec:
validationActions: [Deny]
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: >-
object.spec.containers.all(c,
c.image.startsWith('registry.example.com/')
)
message: "Only images from registry.example.com are allowed."
With Gatekeeper (OPA)
Gatekeeper separates the policy code from its parameters. The
manifest below is a Constraint, in other words an instance: it assumes
that the K8sAllowedReposConstraintTemplate, provided by the official
gatekeeper-library, has already been applied to the cluster. Without it, the
API rejects the manifest as an unknown resource. The repos field works by
string prefix, not by regular expression: an entry without a trailing
slash would also allow a registry whose name starts the same way.
Scanning an image looks for known vulnerabilities in packages. It
says nothing about a manifest declaring privileged: true or mounting the
node's root: the package can be perfectly up to date, the deployment
stays dangerous. The CKS syllabus names the two reference tools,
Kubesec and KubeLinter.
They do not do the same job. Showing it on a deliberately weak
manifest, carrying privileged: true, runAsUser: 0, an image on
latest and a hostPath on the root, is enough to understand which one
to choose:
Tool
What it returns
On the weak manifest
Once hardened
kube-linter 0.8.3
a list of named defects, with the remediation
8 errors, exit 1
2 errors left
kubesec 2.14.2
a numeric score and a verdict
-30 points, exit 2
+13, "Passed", exit 0
Swipe to see more
Terminal window
kube-linterlintdeployment.yaml
kubesecscandeployment.yaml
Remember the difference in use: kube-linter lists, which helps fix; kubesec scores, which helps set a threshold in continuous integration.
Two neighbouring tools do the same work from another angle:
kube-score (in French),
designed for continuous integration, and
Polaris (in French), which
watches a live cluster continuously.
7. Verifying the platform binaries
The supply chain does not stop at images: the kubectl you install on an
administration workstation is part of it. The CKS syllabus asks for it
explicitly, "verify platform binaries before deploying".
Kubernetes publishes the checksum of each binary next to the binary:
The exit code is what counts, not the message. Measured on the 1.37.0
binary: 0 when the file is intact, 1 as soon as it is tampered with, and a
single byte added to a 61 MB binary is enough to flip it. In a script, test this
code rather than looking for the word OK in the output.
Full supply chain workflow
In production and for the CKS, here is the recommended cycle, from the build
to the admission of the image into the cluster:
Build with a minimal base image (distroless or alpine) and a multi-stage build
Scan the image before pushing: grype myapp:1.0.0 --fail-on high
Generate the SBOM and attach it to the image: syft myapp:1.0.0 -o cyclonedx-json
Pin by digest: reference the image by its SHA256 to guarantee immutability
Sign by digest: cosign sign --key cosign.key registry/myapp@sha256:abc123...
Push to a private, controlled artifact repository, an approved internal registry
Verify at admission: Kyverno or Gatekeeper policy, or ImagePolicyWebhook
Troubleshooting
The four situations below share one trait: the cluster behaves exactly as it
was configured, and it is the configuration that is the problem. The
first two rows describe visible blocks; the last two are more
insidious, because nothing fails: the pipeline stays green while the
expected control never happened. Check those first if your chain seems
"too" quiet.
Symptom
Likely cause
Solution
Pod blocked by ImagePolicyWebhook
Webhook unreachable with defaultAllow: false
Check the webhook's availability
Signature not verified
Wrong public key or re-tagged image
Re-sign or use the SHA256 digest
CRITICAL CVEs not blocking
Threshold not configured
Add --fail-on critical in the CI/CD
latest images in production
Permissive tag convention
Kyverno policy forbidding latest
Swipe to see more
Key points
Minimal images: distroless > alpine > ubuntu, multi-stage build; scratch reduces the base footprint but security depends on the shipped binary
Scan before push: Grype (my production choice, EPSS, KEV, OpenVEX) or Trivy (ecosystem culture)
Scanning is not enough: an image without CVEs can still be malicious or compromised, complete it with signing and admission
SBOM: an exhaustive inventory with Syft, re-scannable separately without a rebuild
Signing: Cosign, preferably by digest and keyless in production
Admission control: Kyverno or Gatekeeper as the modern way; ImagePolicyWebhook as the historical mechanism
Registries: restrict to trusted sources, prefer an internal artifact repository
CI/CD: avoid installing tools on the fly from the Internet in production
Test your knowledge
The questions below only cover the supply chain domain. They deliberately
stress the distinctions the exam exploits: scan versus signature, tag
versus digest, and admission control versus CI control.
Knowledge check
Check what you have learned with this interactive quiz
8 questions
8 min
70% to pass
How it works
The timer starts when you click Start
Multiple choice, true/false and short answer questions
You can move between questions
Detailed results are shown at the end
Starts the quiz and the timer
⏱️--:--
0 / 8
Review
(0/0)
--:--
0%
0Correct
0To review
0:00Time
-Level
Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Continue the course: Container security: images, clusters, runtime
A new full quiz with random questions
Work again only on the questions you missed
Put it into practice
Three links of the chain, each validated by a refusal. You sign an image with cosign and prove that the same key refuses the other one, fix the five findings of a Dockerfile static analysis without changing what the image does, and pin a Deployment on the digest actually resolved by the runtime, not on a digest that merely looks like it.