How to Scan Container Images for Vulnerabilities
Implement container image scanning in Kubernetes using Trivy. Learn to scan images in CI/CD, admission controllers, and runtime.
The Problem
You need to detect security vulnerabilities in container images before and after deploying them to Kubernetes.
The Solution
Use Trivy, a comprehensive security scanner, to scan images in CI/CD pipelines and as an admission controller in Kubernetes.
Installing Trivy
Local Installation
# macOS
brew install trivy
# Linux
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
# Verify
trivy versionScanning Images Locally
Basic Scan
trivy image nginx:latestScan with Severity Filter
trivy image --severity HIGH,CRITICAL nginx:latestScan with Exit Code (for CI/CD)
trivy image --exit-code 1 --severity CRITICAL nginx:latestOutput Formats
# JSON output
trivy image -f json nginx:latest > results.json
# Table output
trivy image -f table nginx:latest
# SARIF for GitHub Security
trivy image -f sarif nginx:latest > results.sarifCI/CD Integration
GitHub Actions
name: Scan Container Image
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myapp:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'GitLab CI
scan:
image:
name: aquasec/trivy:latest
entrypoint: [""]
script:
- trivy image --exit-code 1 --severity CRITICAL $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
only:
- mainKubernetes Admission Controller
Install Trivy Operator
helm repo add aqua https://aquasecurity.github.io/helm-charts/
helm repo update
helm install trivy-operator aqua/trivy-operator \
--namespace trivy-system \
--create-namespace \
--set trivy.ignoreUnfixed=trueVulnerabilityReports
The operator automatically creates VulnerabilityReports:
# List all vulnerability reports
kubectl get vulnerabilityreports -A
# View detailed report
kubectl describe vulnerabilityreport -n default myapp-pod-myappView Vulnerabilities
# Get summary
kubectl get vulnerabilityreports -A -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}: Critical={.report.summary.criticalCount}, High={.report.summary.highCount}{"\n"}{end}'Scan Kubernetes Cluster
Scan Running Pods
# Scan all images in a namespace
trivy k8s --namespace default
# Scan entire cluster
trivy k8s --all-namespaces
# Generate report
trivy k8s --report summaryScan with CIS Benchmarks
trivy k8s --compliance k8s-cisBlock Vulnerable Images
Using Kyverno Policy
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: block-vulnerable-images
spec:
validationFailureAction: Enforce
background: false
rules:
- name: check-vulnerabilities
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "*"
attestations:
- type: https://trivy.dev/scan/v1
conditions:
- all:
- key: "{{ criticalCount }}"
operator: Equals
value: "0"Using OPA Gatekeeper
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
name: require-scanned-images
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
repos:
- "gcr.io/verified-images/"
- "docker.io/library/"Scan Filesystem and Config
Scan IaC Files
# Scan Kubernetes manifests
trivy config ./kubernetes/
# Scan Terraform
trivy config ./terraform/
# Scan Dockerfile
trivy config ./DockerfileExample Output
Dockerfile (dockerfile)
=======================
Tests: 23 (SUCCESSES: 21, FAILURES: 2)
Failures: 2 (UNKNOWN: 0, LOW: 0, MEDIUM: 2, HIGH: 0, CRITICAL: 0)
MEDIUM: Specify version tag for image
ββββββββββββββββββββββββββββββββββββ
Using latest tag can cause unexpected behaviorCreate Scan Job
Run as a Kubernetes Job:
apiVersion: batch/v1
kind: Job
metadata:
name: image-scan
spec:
template:
spec:
containers:
- name: trivy
image: aquasec/trivy:latest
args:
- image
- --exit-code
- "1"
- --severity
- CRITICAL,HIGH
- nginx:latest
restartPolicy: Never
backoffLimit: 0Ignore Specific CVEs
Create .trivyignore:
# Ignore specific CVEs
CVE-2023-12345
CVE-2023-67890
# With expiration
CVE-2023-11111 exp:2024-01-01Use it:
trivy image --ignorefile .trivyignore nginx:latestBest Practices
1. Scan in CI/CD Pipeline
Block deployments with critical vulnerabilities.
2. Use Fixed Image Tags
# Bad
image: nginx:latest
# Good
image: nginx:1.25.3-alpine3. Regular Re-scanning
New CVEs are discovered daily. Rescan deployed images regularly.
4. Use Minimal Base Images
# Instead of
FROM ubuntu:22.04
# Use
FROM alpine:3.19
# Or
FROM gcr.io/distroless/static-debian125. Update Dependencies Regularly
Set up automated dependency updates with Dependabot or Renovate.
Key Takeaways
- Scan images before deploying to production
- Integrate scanning in CI/CD pipelines
- Use admission controllers to block vulnerable images
- Run periodic scans on deployed workloads
- Keep base images minimal and updated
π Go Further with Kubernetes Recipes
Love this recipe? Thereβs so much more! This is just one of 100+ hands-on recipes in our comprehensive Kubernetes Recipes book.
Inside the book, youβll master:
- β Production-ready deployment strategies
- β Advanced networking and security patterns
- β Observability, monitoring, and troubleshooting
- β Real-world best practices from industry experts
βThe practical, recipe-based approach made complex Kubernetes concepts finally click for me.β
π Get Your Copy Now β Start building production-grade Kubernetes skills today!
π Get All 100+ Recipes in One Book
Stop searching β get every production-ready pattern with detailed explanations, best practices, and copy-paste YAML.
Want More Kubernetes Recipes?
This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.