Why Container Security Matters
The Vulnerability Problem
Container images are a critical attack surface in modern deployments.
Every time you build a container image, it includes the base OS layer, runtime libraries, dependencies, and your application code. Each layer can contain known vulnerabilities. Without scanning, vulnerable images reach production and expose your infrastructure to attacks.
Real Statistics
- 80% of container images in production contain at least one known vulnerability
- Supply chain attacks targeting container registries are increasing
- Unpatched container vulnerabilities lead to data breaches and service disruptions
The Security Challenge
Why Manual Scanning Isn’t Enough
Manual security reviews don’t scale. You can’t realistically inspect every dependency in every container layer. Without automation, vulnerable images slip through, and by the time they’re discovered in production, the damage is already done.
Common Vulnerabilities in Containers
- Outdated base images with unpatched OS vulnerabilities
- Vulnerable dependencies from npm, pip, pip, Maven packages
- Exposed secrets accidentally included in image layers
- Misconfigurations creating insecure defaults
- Malware hidden in supply chain attacks
The Solution: Trivy
What is Trivy?
Trivy is a simple, fast, and comprehensive container vulnerability scanner created by Aqua Security. It scans container images, filesystems, and configuration files for vulnerabilities, misconfigurations, and secrets.
Why Choose Trivy?
- Speed: Scans images in seconds, not minutes
- Accuracy: Supports multiple vulnerability databases (NVD, GitHub Security, Aqua, Alpine)
- Comprehensive: Detects OS vulnerabilities, application dependencies, and misconfigurations
- Zero setup: Works out of the box without complex configuration
- CI/CD ready: Integrates easily into GitHub Actions, GitLab CI, Jenkins
- Open-source: Free, transparent, and community-driven
Installation and Setup
Install Trivy
# macOSbrew install trivy
# Linux (Ubuntu/Debian)wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | apt-key add -echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | tee -a /etc/apt/sources.list.d/trivy.listapt-get updateapt-get install trivy
# Dockerdocker pull aquasec/trivyBasic Image Scanning
# Scan a local imagetrivy image my-app:latest
# Scan from registrytrivy image nginx:latest
# Scan with detailed outputtrivy image --severity HIGH,CRITICAL my-app:latestSetting Severity Thresholds
Scan with Specific Severity Levels
# Only show critical and high severity issuestrivy image --severity CRITICAL,HIGH my-app:latest
# Exit with error code if vulnerabilities foundtrivy image --severity HIGH,CRITICAL --exit-code 1 my-app:latestOutput Formats
# JSON output for parsingtrivy image --format json my-app:latest
# SARIF format for GitHub integrationtrivy image --format sarif my-app:latest
# Table format (default)trivy image --format table my-app:latestCI/CD Integration
GitHub Actions Workflow
name: Container Vulnerability Scan
on: push: branches: [main] paths: - 'Dockerfile' - 'src/**' pull_request: branches: [main]
jobs: trivy-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3
- name: Set up Docker Buildx uses: docker/setup-buildx-action@v2
- name: Build Docker image uses: docker/build-push-action@v4 with: context: . file: ./Dockerfile push: false load: true tags: my-app:${{ github.sha }}
- name: Run Trivy vulnerability scan uses: aquasecurity/trivy-action@master with: image-ref: my-app:${{ github.sha }} format: 'sarif' output: 'trivy-results.sarif' severity: 'CRITICAL,HIGH'
- name: Upload Trivy results to GitHub Security uses: github/codeql-action/upload-sarif@v2 with: sarif_file: 'trivy-results.sarif'
- name: Fail if critical vulnerabilities found run: | trivy image --severity CRITICAL my-app:${{ github.sha }} --exit-code 1GitLab CI Integration
stages: - build - scan
build: stage: build image: docker:latest services: - docker:dind script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
scan: stage: scan image: aquasec/trivy:latest script: - trivy image --severity HIGH,CRITICAL --exit-code 1 $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA allow_failure: falsePolicy Enforcement
Create a Trivy Policy
# Define what constitutes a vulnerability violationseverity: HIGH,CRITICAL
# Ignore specific CVEs for known/accepted risksignorefile: .trivyignore
# Policy for failing buildsexit-code: 1
# Require sign-off for medium severitymedium-requires-approval: trueIgnoring False Positives
# Format: CVE-XXXX-XXXXX [optional: expiration date]
# Known false positive or acceptable risk (expires 2026-12-31)CVE-2024-1234 2026-12-31
# Permanently ignore (use with caution)CVE-2024-5678Advanced Scanning Strategies
Scan Filesystems
# Scan local directorytrivy fs .
# Scan with detailed outputtrivy fs --severity HIGH,CRITICAL --format json . > fs-scan.jsonScan Configuration Files
# Detect misconfigurations in Dockerfiletrivy config Dockerfile
# Scan Kubernetes manifeststrivy config k8s-manifests/Generate Software Bill of Materials (SBOM)
# Generate SBOM in CycloneDX formattrivy image --format cyclonedx my-app:latest > sbom.xml
# Generate SBOM in SPDX formattrivy image --format spdx my-app:latest > sbom.spdxRemediation Workflow
When Vulnerabilities Are Found
- Minimal Approach: Update base image
# BeforeFROM ubuntu:20.04
# AfterFROM ubuntu:22.04- Direct Approach: Update vulnerable dependency
FROM node:18-alpine
# Install with security patchesRUN npm install --no-save my-package@latest- Complete Approach: Rebuild entire image
docker build --no-cache -t my-app:latest .Real-World Example: Multi-Stage Scanning
name: Production Container Security
on: schedule: # Run daily scans - cron: '0 2 * * *' workflow_dispatch:
jobs: scan-all-images: runs-on: ubuntu-latest strategy: matrix: image: - my-app:latest - api-gateway:latest - worker-service:latest
steps: - uses: aquasecurity/trivy-action@master with: image-ref: ${{ matrix.image }} format: 'json' output: 'trivy-${{ matrix.image }}.json' severity: 'CRITICAL,HIGH,MEDIUM'
- name: Archive results uses: actions/upload-artifact@v3 with: name: trivy-reports path: trivy-*.json
- name: Notify security team if: failure() run: | curl -X POST -H 'Content-type: application/json' \ --data '{"text":"Critical vulnerabilities found in ${{ matrix.image }}"}' \ ${{ secrets.SLACK_WEBHOOK_URL }}Monitoring and Reporting
Store Results Over Time
# Generate timestamped reportsTIMESTAMP=$(date +%Y%m%d-%H%M%S)trivy image --format json my-app:latest > reports/scan-$TIMESTAMP.jsonTrack Vulnerability Trends
#!/bin/bash# Count vulnerabilities by severitytrivy image --format json my-app:latest | \ jq '[.Results[]?.Vulnerabilities[]?.Severity] | group_by(.) | map({severity: .[0], count: length})'Best Practices
1. Scan Early and Often
- Scan during development (local images)
- Scan in CI/CD pipeline (before merge)
- Scan in registry (continuous monitoring)
- Scan in production (runtime detection)
2. Use Minimal Base Images
# Reduce attack surfaceFROM alpine:3.18 as baseFROM gcr.io/distroless/base-debian113. Regular Dependency Updates
# Update dependencies regularlynpm audit fix --forcepython -m pip install --upgrade pip4. Maintain SBOMs
Generate and store SBOMs for supply chain transparency:
trivy image --format cyclonedx my-app:latest > sbom.jsongit add sbom.jsongit commit -m "Update SBOM for security tracking"Integration with Artifact Registry
Push Only Scanned Images
# Only push if scan passestrivy image --severity CRITICAL,HIGH --exit-code 1 my-app:latest && \ docker push my-registry/my-app:latestConclusion
Container vulnerability scanning is non-negotiable in modern DevSecOps.
Trivy makes it simple to detect and prevent vulnerable images from reaching production. Integrate it into your CI/CD pipeline, set enforcement policies, and maintain a culture of continuous security improvement.