Skip to main content
COSMICBYTEZLABS
NewsSecurityHOWTOsToolsStudyTraining
ProjectsChecklistsAI RankingsNewsletterStatusTagsAbout
Subscribe

Press Enter to search or Esc to close

News
Security
HOWTOs
Tools
Study
Training
Projects
Checklists
AI Rankings
Newsletter
Status
Tags
About
RSS Feed
Reading List
Subscribe

Stay in the Loop

Get the latest security alerts, tutorials, and tech insights delivered to your inbox.

Subscribe NowFree forever. No spam.
COSMICBYTEZLABS

Your trusted source for IT intelligence, cybersecurity insights, and hands-on technical guides.

429+ Articles
114+ Guides

CONTENT

  • Latest News
  • Security Alerts
  • HOWTOs
  • Projects
  • Exam Prep

RESOURCES

  • Search
  • Browse Tags
  • Newsletter Archive
  • Reading List
  • RSS Feed

COMPANY

  • About Us
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CosmicBytez Labs. All rights reserved.

System Status: Operational
  1. Home
  2. HOWTOs
  3. Container Security Scanning with Trivy: Images, IaC, and CI/CD
Container Security Scanning with Trivy: Images, IaC, and CI/CD
HOWTOIntermediate

Container Security Scanning with Trivy: Images, IaC, and CI/CD

Learn how to use Trivy to scan container images, Dockerfiles, Kubernetes manifests, and Terraform for vulnerabilities and misconfigurations — then integrate it into your GitHub Actions pipeline.

Dylan H.

Tutorials

March 27, 2026
7 min read

Prerequisites

  • Docker Engine installed and running
  • Basic command-line proficiency
  • Familiarity with container concepts (images, registries, layers)
  • GitHub repository (optional, for CI/CD integration)

Introduction

Containers ship fast — and they carry their vulnerabilities with them. A single FROM ubuntu:20.04 layer can contain dozens of known CVEs, and misconfigured Kubernetes manifests are a leading cause of cloud breaches. Without automated scanning, these issues only surface after an incident.

Trivy (pronounced "try-vee") is an open-source, all-in-one security scanner from Aqua Security. A single tool covers:

  • OS package vulnerabilities — Alpine, Debian, Ubuntu, RHEL, Amazon Linux, and more
  • Language ecosystem dependencies — npm, pip, Maven, Go modules, Cargo, Composer, NuGet
  • Infrastructure as Code misconfigurations — Dockerfile, Kubernetes YAML, Terraform, CloudFormation, Helm
  • Exposed secrets — API keys, tokens, certificates, database connection strings (150+ pattern types)
  • License compliance — detect GPL/AGPL code in commercial projects

In this guide you'll go from a fresh Trivy install to a fully integrated scanning pipeline that catches issues before they reach production.


Prerequisites

Before starting, confirm you have:

  • Docker Engine 20.10+ (docker --version)
  • curl or a package manager for installation
  • 4 GB free disk space for the Trivy vulnerability database
  • A container image to practice with (we use public images throughout)

Step 1: Install Trivy

Option A — Install Script (Linux / macOS)

curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \
  | sh -s -- -b /usr/local/bin v0.58.0

Verify:

trivy --version
# trivy version 0.58.0

Option B — Package Manager

Debian / Ubuntu:

sudo apt-get install -y wget apt-transport-https gnupg lsb-release
 
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key \
  | gpg --dearmor \
  | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null
 
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] \
  https://aquasecurity.github.io/trivy-repo/deb \
  $(lsb_release -sc) main" \
  | sudo tee /etc/apt/sources.list.d/trivy.list
 
sudo apt-get update && sudo apt-get install -y trivy

RHEL / CentOS / Rocky Linux:

RELEASE=$(grep -Po '(?<=VERSION_ID=")[0-9]' /etc/os-release)
sudo tee /etc/yum.repos.d/trivy.repo << EOF
[trivy]
name=Trivy repository
baseurl=https://aquasecurity.github.io/trivy-repo/rpm/releases/$RELEASE/\$basearch/
gpgcheck=1
enabled=1
gpgkey=https://aquasecurity.github.io/trivy-repo/rpm/public.key
EOF
sudo yum -y install trivy

macOS (Homebrew):

brew install trivy

Option C — Docker (Zero Install)

Run Trivy as a container without installing anything locally:

docker run --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v $HOME/.cache/trivy:/root/.cache/trivy \
  aquasec/trivy:latest image nginx:latest

Mounting the Docker socket lets Trivy access locally built images. The cache volume prevents re-downloading the vulnerability database on every run.


Step 2: Your First Container Scan

Basic Image Scan

trivy image nginx:latest

On first run, Trivy downloads the vulnerability database (~200 MB). Subsequent runs use the local cache.

Output is grouped by layer/package and sorted by severity:

nginx:latest (debian 12.5)
==========================
Total: 142 (UNKNOWN: 0, LOW: 88, MEDIUM: 31, HIGH: 19, CRITICAL: 4)

┌──────────────────────┬────────────────┬──────────┬──────────────────────┬───────────────────┐
│       Library        │  Vulnerability │ Severity │  Installed Version   │   Fixed Version   │
├──────────────────────┼────────────────┼──────────┼──────────────────────┼───────────────────┤
│ libssl3              │ CVE-2024-5535  │ CRITICAL │ 3.0.11-1~deb12u2     │ 3.0.13-1~deb12u1  │
│ openssl              │ CVE-2024-5535  │ CRITICAL │ 3.0.11-1~deb12u2     │ 3.0.13-1~deb12u1  │
...

Filter to Critical and High Only

trivy image --severity CRITICAL,HIGH nginx:latest

This dramatically reduces noise, focusing your team on what matters most.

Compare Base Images

Alpine images carry far fewer OS vulnerabilities than Debian/Ubuntu:

# Debian-based — many findings
trivy image --severity CRITICAL,HIGH nginx:latest
 
# Alpine-based — minimal findings
trivy image --severity CRITICAL,HIGH nginx:1.26-alpine

Use this to make an informed base image choice early in your Dockerfile.

Scan a Locally Built Image

docker build -t myapp:dev .
trivy image --severity CRITICAL,HIGH myapp:dev

Scan a Private Registry Image

AWS ECR:

aws ecr get-login-password --region us-east-1 \
  | docker login --username AWS --password-stdin \
    123456789012.dkr.ecr.us-east-1.amazonaws.com
 
trivy image 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:latest

Azure Container Registry:

az acr login --name myregistry
trivy image myregistry.azurecr.io/myapp:latest

Google Artifact Registry:

gcloud auth configure-docker us-central1-docker.pkg.dev
trivy image us-central1-docker.pkg.dev/myproject/myrepo/myapp:latest

Step 3: Output Formats

Trivy supports multiple output formats for integration with other tools.

# Default table output
trivy image nginx:latest
 
# JSON (for programmatic processing or dashboards)
trivy image --format json --output trivy-report.json nginx:latest
 
# SARIF (upload to GitHub Security tab / Azure DevOps)
trivy image --format sarif --output trivy-results.sarif nginx:latest
 
# CycloneDX SBOM (Software Bill of Materials)
trivy image --format cyclonedx --output sbom.json nginx:latest
 
# SPDX SBOM
trivy image --format spdx-json --output sbom-spdx.json nginx:latest
 
# HTML report
trivy image \
  --format template \
  --template "@contrib/html.tpl" \
  --output report.html \
  nginx:latest

The SARIF format is particularly useful — GitHub natively renders SARIF findings in the Security tab of any repository.


Step 4: Scan Infrastructure as Code

Dockerfile Scanning

Create a deliberately insecure Dockerfile to test with:

# insecure.Dockerfile
FROM ubuntu:18.04
USER root
COPY . /app
RUN apt-get update && apt-get install -y curl wget
EXPOSE 22
CMD ["/bin/bash"]

Scan it:

trivy config --file-patterns "Dockerfile:insecure.Dockerfile" .

Trivy will flag issues like running as root, exposing port 22, and using a deprecated base image.

Kubernetes Manifest Scanning

# Scan a single manifest
trivy config deployment.yaml
 
# Scan an entire manifests directory
trivy config ./k8s/
 
# Scan a Helm chart
trivy config ./charts/myapp/

Common findings in Kubernetes manifests:

  • AVD-KSV-0014 — Container running as root (runAsUser: 0)
  • AVD-KSV-0011 — CPU limits not set
  • AVD-KSV-0016 — Privileged container (privileged: true)
  • AVD-KSV-0030 — Default service account token mounted unnecessarily

Example secure pod security context:

securityContext:
  runAsNonRoot: true
  runAsUser: 1000
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
  capabilities:
    drop: ["ALL"]

Terraform Scanning

trivy config ./terraform/

Trivy checks Terraform for misconfigurations like:

  • S3 buckets without server-side encryption or versioning
  • Security groups with 0.0.0.0/0 ingress on sensitive ports
  • RDS instances without encryption at rest
  • Lambda functions with overly permissive IAM roles
  • CloudTrail logging disabled

Step 5: Secret Detection

Trivy scans for accidentally committed secrets across filesystems and Git repositories.

# Scan a local directory
trivy fs --scanners secret .
 
# Scan a Git repo (clones and scans)
trivy repo https://github.com/myorg/myrepo
 
# Combine vuln, config, and secret scanning
trivy fs --scanners vuln,config,secret .

Trivy detects 150+ secret patterns including: AWS access keys, GitHub personal access tokens, Slack webhooks, JWT secrets, private RSA/EC keys, database connection strings, and SendGrid/Twilio/Stripe API keys.

Test Secret Detection

# Create a harmless test file (fake key format, safe to use in testing)
echo 'AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' > /tmp/test-secret.txt
trivy fs --scanners secret /tmp/test-secret.txt
rm /tmp/test-secret.txt

You should see a HIGH severity finding for the AWS secret.


Step 6: GitHub Actions Integration

Add Trivy to your CI/CD pipeline to block vulnerable images and misconfigured IaC before they merge.

Create .github/workflows/trivy-scan.yml:

name: Trivy Security Scan
 
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  schedule:
    - cron: "0 6 * * 1"  # Weekly Monday 6am UTC
 
permissions:
  contents: read
  security-events: write  # Required to upload SARIF results
 
jobs:
  scan-image:
    name: Container Image Scan
    runs-on: ubuntu-latest
 
    steps:
      - name: Checkout
        uses: actions/checkout@v4
 
      - name: Build image
        run: docker build -t myapp:${{ github.sha }} .
 
      - name: Cache Trivy DB
        uses: actions/cache@v4
        with:
          path: ~/.cache/trivy
          key: trivy-db-${{ github.run_id }}
          restore-keys: trivy-db-
 
      - name: Scan image with Trivy
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: myapp:${{ github.sha }}
          format: sarif
          output: trivy-image.sarif
          severity: CRITICAL,HIGH
          exit-code: "1"       # Fail the build on findings
          ignore-unfixed: true # Skip CVEs without a fix available
 
      - name: Upload results to GitHub Security tab
        uses: github/codeql-action/upload-sarif@v3
        if: always()           # Upload even when the scan step fails
        with:
          sarif_file: trivy-image.sarif
          category: container-image
 
  scan-iac:
    name: IaC and Secret Scan
    runs-on: ubuntu-latest
 
    steps:
      - name: Checkout
        uses: actions/checkout@v4
 
      - name: Cache Trivy DB
        uses: actions/cache@v4
        with:
          path: ~/.cache/trivy
          key: trivy-db-${{ github.run_id }}
          restore-keys: trivy-db-
 
      - name: Scan IaC configs
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: config
          scan-ref: "."
          format: sarif
          output: trivy-iac.sarif
          exit-code: "1"
 
      - name: Scan for secrets
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: fs
          scan-ref: "."
          scanners: secret
          format: sarif
          output: trivy-secrets.sarif
          exit-code: "1"
 
      - name: Upload IaC results
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: trivy-iac.sarif
          category: iac
 
      - name: Upload secret scan results
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: trivy-secrets.sarif
          category: secrets

After the first run, findings appear under Security → Code scanning alerts in your GitHub repository.


Step 7: Advanced Configuration

.trivyignore — Suppress False Positives

Create a .trivyignore file at the repository root to suppress accepted risks:

# .trivyignore
# Format: <ID> [exp:<YYYY-MM-DD>] [ack:<note>]
 
# No fix available; mitigated by network segmentation policy
CVE-2023-44487
 
# Accepted false positive — only triggered in a path we don't execute
CVE-2024-21626 exp:2026-09-01 ack:reviewed by security team 2026-03-15
 
# IaC check — intentional, Terraform state bucket (no versioning needed)
AVD-AWS-0090
 
# Suppress a whole check type in a specific file
AVD-KSV-0020

Reference the file when scanning:

trivy image --ignorefile .trivyignore myapp:latest

trivy.yaml — Reusable Configuration

Avoid repeating flags by creating a trivy.yaml config file:

# trivy.yaml
scan:
  security-checks:
    - vuln
    - config
    - secret
 
severity:
  - CRITICAL
  - HIGH
 
format: table
exit-code: 0        # 0 for local dev; override to 1 in CI
 
image:
  ignore-unfixed: true
 
db:
  skip-update: false
 
cache:
  dir: ~/.cache/trivy

Use it with any scan command:

trivy image --config trivy.yaml myapp:latest
trivy config --config trivy.yaml ./terraform/

Air-Gapped / Offline Environments

In environments without internet access, pre-download the database and transfer it:

# On an internet-connected machine — download the DB bundle
trivy image --download-db-only
tar czf trivy-db.tar.gz ~/.cache/trivy/db/
 
# Transfer trivy-db.tar.gz to the air-gapped host, then:
mkdir -p ~/.cache/trivy/db/
tar xzf trivy-db.tar.gz -C ~/.cache/
 
# Scan without updating the DB
trivy image --skip-db-update --offline-scan myapp:latest

Verification

Run these checks to confirm your Trivy setup is working correctly.

1. Confirm Vulnerability Detection

Use a known-vulnerable image (safe to pull, just don't run it):

trivy image --severity CRITICAL python:3.6-slim 2>/dev/null | grep "CRITICAL"

You should see multiple CRITICAL findings — Python 3.6 is end-of-life with many unpatched CVEs.

2. Confirm IaC Scanning Works

cat > /tmp/test-pod.yaml << 'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: insecure-pod
spec:
  containers:
  - name: app
    image: nginx:latest
    securityContext:
      privileged: true
      runAsUser: 0
EOF
 
trivy config /tmp/test-pod.yaml
rm /tmp/test-pod.yaml

Expected: CRITICAL/HIGH findings for privileged: true and root user.

3. Confirm Secret Scanning Works

cat > /tmp/test-creds.env << 'EOF'
# Fake credentials — safe test data only
GITHUB_TOKEN=ghp_aBcDeFgHiJkLmNoPqRsTuVwXyZ123456
DATABASE_URL=postgresql://admin:s3cr3tpassword@db.internal/prod
EOF
 
trivy fs --scanners secret /tmp/test-creds.env
rm /tmp/test-creds.env

Expected: HIGH/CRITICAL findings for GitHub token and database credentials.

4. Verify GitHub Actions Results

After pushing your workflow:

gh run list --workflow=trivy-scan.yml --limit 5
gh run view <run-id> --log

Navigate to Security → Code scanning alerts in GitHub to confirm SARIF results uploaded successfully.


Troubleshooting

Database Download Fails

# Test connectivity to the Trivy DB registry
curl -I https://ghcr.io
 
# Force a fresh database download
trivy image --clear-cache nginx:latest
 
# Use the AWS mirror (useful behind proxies)
trivy image \
  --db-repository public.ecr.aws/aquasecurity/trivy-db \
  nginx:latest

"No Such Image" Error

# Pull the image first, then scan
docker pull myapp:latest
trivy image myapp:latest
 
# Or tell Trivy to pull from the registry directly
trivy image --image-src registry myapp:latest

Scan Is Slow or Times Out

# Reduce parallelism to avoid memory pressure
trivy image --parallel 1 myapp:latest
 
# Skip specific large directories (e.g., bundled JDK)
trivy image --skip-dirs /usr/local/java myapp:latest
 
# Only scan OS packages (skip language dependencies)
trivy image --vuln-type os myapp:latest

Too Many False Positives in CI

# Skip CVEs that have no available fix
trivy image --ignore-unfixed myapp:latest
 
# Add a project-wide .trivyignore for accepted risks
echo "CVE-2024-XXXXX exp:2026-12-31 ack:no-fix-upstream" >> .trivyignore
 
# Set a minimum score threshold
trivy image --severity CRITICAL myapp:latest

GitHub Actions SARIF Upload Rejected

The workflow job needs security-events: write permission. Confirm the permissions block is present:

permissions:
  contents: read
  security-events: write

If your organization restricts third-party Actions, pin the action to a specific SHA:

uses: aquasecurity/trivy-action@a20de5420d57c4102486cdd9349b532bf5b12c7  # v0.20.0

Summary

You've built a full container security scanning pipeline with Trivy:

TaskCommand
Scan a container imagetrivy image --severity CRITICAL,HIGH myapp:latest
Scan Dockerfile / K8s / Terraformtrivy config ./infra/
Detect committed secretstrivy fs --scanners secret .
Generate SARIF for GitHub--format sarif --output results.sarif
Scan a Git repositorytrivy repo https://github.com/org/repo
Suppress known false positives.trivyignore file
Offline / air-gapped scanning--skip-db-update --offline-scan

Trivy's breadth — one binary covering OS packages, app dependencies, IaC misconfigurations, and secrets — makes it the go-to choice for DevSecOps teams. Start with image scanning and severity filtering in CI, then progressively add IaC and secret detection as your pipeline matures.

Where to go next:

  • Run trivy k8s --report summary cluster to scan a live Kubernetes cluster
  • Deploy the Trivy Operator for continuous in-cluster scanning with automatic policy enforcement
  • Integrate Trivy with Harbor or AWS ECR to block vulnerable images at the registry level before they ever reach a deployment pipeline
#docker#security#devsecops#containers#vulnerability-scanning#kubernetes#github-actions#cicd

Related Articles

HashiCorp Vault: Centralized Secrets Management for Modern Infrastructure

Deploy and configure HashiCorp Vault to securely store, rotate, and audit secrets across your infrastructure — covering installation, auth methods,...

8 min read

How to Deploy Falco for Kubernetes Runtime Security Monitoring

Step-by-step guide to deploying Falco as a Kubernetes runtime security engine. Covers Helm installation, custom rule authoring, Falcosidekick alerting...

12 min read

How to Deploy Wazuh SIEM/XDR for Unified Security Monitoring

Step-by-step guide to deploying Wazuh as an open-source SIEM and XDR platform. Covers server installation, agent deployment across Windows and Linux,...

13 min read
Back to all HOWTOs