Scanning Basics
This section introduces container image scanning: finding known vulnerabilities in an image before it ships, then blocking bad images at the Kubernetes admission gate.
Prerequisites
- Trivy (Aqua Security) or Grype (Anchore) - the two most common open-source scanners.
- Docker Engine 29.x with BuildKit to build the image you will scan.
- Kubernetes 1.36.2 cluster with Kyverno or OPA Gatekeeper for admission policies.
- cosign (Sigstore) if you also want to sign and verify images.
Quick install of Trivy on Debian/Ubuntu:
# Install Trivy from the Aqua Security apt repo
sudo apt-get install -y wget gnupg
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo gpg --dearmor -o /usr/share/keyrings/trivy.gpg
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" | sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt-get update && sudo apt-get install -y trivyBasic Examples
1. Scan a local image
Run Trivy against an image you just built to list known CVEs by severity.
trivy image myapp:1.4.0- Trivy pulls the vulnerability database, reads the image layers, and enumerates OS packages plus language dependencies.
- Findings are grouped by severity: UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL.
- The first run downloads the DB; later runs are fast because the DB is cached.
- No cluster is needed - this works on any machine with the image available.
2. Fail a build on critical findings
Make the scanner return a non-zero exit code so CI stops the pipeline.
trivy image --severity HIGH,CRITICAL --exit-code 1 myapp:1.4.0--exit-code 1turns findings into a failed step, which blocks the merge or deploy.--severitylimits what counts as a failure so low-noise findings do not break builds.- This is the core of shift-left: reject the vulnerable image before it reaches a registry.
- Add
--ignore-unfixedto skip CVEs that have no available patch yet.
3. Scan the filesystem or a Dockerfile
Catch problems in dependencies and configuration without building an image.
trivy fs --scanners vuln,misconfig .trivy fsscans a project directory for vulnerable lockfile dependencies.- The
misconfigscanner flags insecure Dockerfile and manifest settings, like running as root. - This runs earliest in the pipeline, on the source itself.
- Use it in a pre-commit hook or the first CI stage for the fastest feedback.
4. Generate an SBOM
Produce a software bill of materials so you can map future CVEs to shipped images.
trivy image --format cyclonedx --output sbom.json myapp:1.4.0- The SBOM lists every component and version inside the image in CycloneDX format.
- Store it as a build artifact or attach it to the image as an attestation.
- When a new CVE lands, you query SBOMs instead of re-scanning every image.
- SPDX is also supported via
--format spdx-json.
5. Scan with Grype as an alternative
Grype is a fast, drop-in second opinion that reads the same image.
grype myapp:1.4.0 --fail-on high--fail-on highfails the run on High or Critical findings.- Grype pairs with Syft (its SBOM tool) if you prefer the Anchore toolchain.
- Running two scanners occasionally surfaces a CVE one database has and the other lacks.
- Pick one as your gate; a second is optional defense in depth.
6. Cache the vulnerability database in CI
Avoid re-downloading the DB on every pipeline run.
trivy image --cache-dir .trivycache/ --exit-code 1 --severity CRITICAL myapp:1.4.0- Point
--cache-dirat a path your CI system caches between runs. - This cuts scan time and avoids rate limits on the DB mirror.
- Refresh the cache daily so you do not miss newly published CVEs.
- In air-gapped setups, mirror the DB internally and set
TRIVY_DB_REPOSITORY.
7. Scan a running cluster's workloads
Point Trivy at your cluster to report on images already deployed.
trivy k8s --report summary clustertrivy k8sinspects the images referenced by running workloads.- The summary shows which namespaces and Deployments carry the most risk.
- Use it to find images that were admitted before your gate existed.
- The Trivy Operator can run this continuously and store results as CRDs.
Intermediate Examples
8. Deny critical CVEs at admission with the Trivy Operator
Combine continuous scanning with a policy that blocks vulnerable images from being admitted.
# Kyverno: deny Pods whose image has an unresolved CRITICAL vulnerability report
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: block-critical-cves
spec:
validationFailureAction: Enforce
background: false
rules:
- name: check-vuln-report
match:
any:
- resources:
kinds: ["Pod"]
context:
- name: report
apiCall:
urlPath: "/apis/aquasecurity.github.io/v1alpha1/namespaces/{{request.namespace}}/vulnerabilityreports"
validate:
message: "Image has CRITICAL vulnerabilities and is blocked."
deny:
conditions:
any:
- key: "{{ report.items[].report.summary.criticalCount | sum(@) }}"
operator: GreaterThan
value: 0- The Trivy Operator scans images in-cluster and writes
vulnerabilityreportscustom resources. - Kyverno reads those reports at admission and denies Pods above your threshold.
validationFailureAction: Enforcemakes the deny real; useAuditfirst to see impact.- This closes the loop: scan continuously, then gate on the results.
9. Sign in CI and verify at admission
Prove an image came from your pipeline before allowing it to run.
# In CI, after pushing the image
cosign sign --key env://COSIGN_KEY registry.example.com/myapp:1.4.0cosign signattaches a signature to the image in the registry.- A Kyverno
verifyImagesrule rejects any image lacking a valid signature. - This defends against tampered or typosquatted images even if they scan clean.
- Keep the private key in a KMS or CI secret, never in the repo.
10. Wire the scan into a GitHub Actions gate
Make a failed scan block the pull request automatically.
# .github/workflows/scan.yml (job step)
- name: Scan image
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
severity: HIGH,CRITICAL
exit-code: "1"
ignore-unfixed: "true"- The action fails the job when High or Critical, fixable CVEs are found.
ignore-unfixedavoids blocking on vulnerabilities with no patch available.- Require this check in branch protection so it cannot be merged around.
- Upload the SARIF output to code scanning for a reviewable history.
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).