A hardened image still inherits CVEs from its base and dependencies. Scan images with Trivy in the build, fail on fixable criticals, and re-scan what you already shipped as new CVEs are disclosed.
An image scanner unpacks every layer and inventories the OS packages and language dependencies, then matches each against vulnerability databases — and flags secrets and misconfigurations along the way. It is how you learn your base image shipped a critical OpenSSL CVE you never chose. This overlaps with SCA (Supply Chain course); here the target is the assembled image.
docker build -> layers -> scanner unpacks + inventories:
OS packages (apt/apk/yum) -> matched vs CVE feeds
language deps (npm/pip/gem) -> matched vs CVE feeds
known secrets (keys/tokens left in layers)
misconfigurations (root user, bad perms)
Output: "openssl 3.0.11 -> CVE-2024-xxxx CRITICAL, fixed in 3.0.13".
That's an upgrade you must take, discovered before it ships.Run the scan on the image you just built, before it is pushed or deployed. Fail the build on high/critical CVEs that have a fix available, so a red build is always actionable. Output SARIF to feed the finding into code-scanning dashboards, exactly as the DevSecOps Pipeline course wires up.
# locally
trivy image --severity HIGH,CRITICAL --ignore-unfixed app:1.4.0
# in CI (fail the build, upload results)
image-scan:
steps:
- run: docker build -t app:test .
- uses: aquasecurity/trivy-action@0.24.0
with:
image-ref: app:test
severity: HIGH,CRITICAL
ignore-unfixed: true
exit-code: "1"
format: sarif
output: trivy.sarif
- uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: trivy.sarif }The fix for a vulnerable image is to update the base or dependency and rebuild — never to shell into a running container and patch it, which produces an unscanned, undocumented snowflake. Most image CVEs vanish by bumping the base image tag/digest to a patched release and rebuilding, which your pipeline then re-scans automatically.
# a base-image bump usually clears most OS CVEs at once:
# FROM node:20.11-slim@sha256:OLD...
# ->
# FROM node:20.13-slim@sha256:NEW... # patched base, rebuild + rescan
# do NOT: 'kubectl exec' + apt upgrade in a live pod. That container is
# now a snowflake nobody can reproduce or scan. Rebuild from source.
# distroless/minimal bases have far fewer OS packages, so far fewer of
# these fire in the first place — hardening and scanning reinforce each other.An image that scanned clean at build time can become vulnerable tomorrow when a new CVE is disclosed against a package it contains. Continuously re-scan running images (or their stored SBOMs) so you learn you are exposed without waiting for the next build. Wire the result to alerting so "newly vulnerable in production" becomes a ticket, not a surprise.
# nightly: scan images currently running in the cluster
for img in $(kubectl get pods -A -o jsonpath='{..image}' | tr ' ' '\n' | sort -u); do
trivy image --severity CRITICAL --ignore-unfixed "$img"
done
# better at scale: scan the stored SBOM (no rebuild) — see Supply Chain course:
# grype sbom:sbom.cdx.json --fail-on critical
# alert on any NEW critical against something already in prod.