Introduction
Container images are the building blocks of modern cloud-native applications, but they often ship with known vulnerabilities in base images, system packages, or application dependencies. A single unpatched CVE can expose your entire infrastructure to remote code execution, data breaches, or denial-of-service attacks.
In this tutorial, you’ll learn how to:
- Install and configure Trivy — the most widely adopted open-source container image scanner
- Scan Docker images for OS packages, library vulnerabilities, IaC misconfigurations, and secrets
- Automate security scanning inside GitHub Actions CI/CD pipelines
- Enforce security gates that block vulnerable images from reaching production
Prerequisites:
Docker installed locally
- A GitHub account with a repository
- Basic familiarity with YAML and CI/CD concepts
What is Trivy and Why Container Scanning Matters
Trivy (short for “Trigger” — a vulnerability scanner) is an open-source security scanning tool created by Aqua Security. It has become the industry standard for container image scanning, operating system vulnerability detection, IaC misconfiguration checks, and secrets detection — all with zero configuration required.
Container images are composed of layers — base OS, system packages (apt/apk/yum), language-specific libraries (npm, pip, gem), and application code. Each layer can introduce vulnerabilities. Trivy cross-references package manifests against multiple vulnerability databases including:
NVD (National Vulnerability Database)
Red Hat CVE Database
Debian / Ubuntu Security Advisories
Alpine SecDB
GitHub Advisory Database
Scanning should happen as early as possible in the development lifecycle — this is the “shift left” security principle. Catching a critical vulnerability in a PR prevents it from ever reaching a registry, a runtime environment, or — worst case — production.
The diagram above shows the complete Trivy scanning flow: code push triggers a CI build, the image is scanned, and depending on severity levels, the pipeline either passes (allowing the image to be pushed to a registry) or fails (blocking the vulnerable image).
Architecture Overview
When integrated into a CI/CD pipeline, Trivy sits between the image build step and the image push step. The architecture involves three key stages:
This architecture diagram illustrates how Trivy plugs into your CI/CD pipeline: source code changes trigger a CI runner, which builds the image and passes it to the Trivy scanning engine. The engine cross-references the image layers against its cached vulnerability database and generates reports that can be pushed to GitHub Security, alerting systems, or image attestation tools.
Step 1: Setup — Install Trivy
Trivy can be installed on Linux, macOS, or Windows. It’s also available as a Docker image, which is the preferred method for CI/CD environments.
Option A: Local Installation (macOS / Linux)
# macOS — using Homebrew
brew install trivy
# Ubuntu / Debian
sudo apt-get install trivy
# RHEL / CentOS / Fedora
sudo dnf install trivy
Option B: Docker Image (Recommended for CI/CD)
# Pull the latest version
docker pull aquasec/trivy:latest
# Verify it works
docker run --rm aquasec/trivy:latest --version
Tip: Trivy caches the vulnerability database locally at
~/.cache/trivy. In CI/CD, use Docker volume mounts to persist the cache between runs for faster scans.
Step 2: Scan Container Images
Once installed, scanning a local Docker image is a single command. Let’s scan a Python image to see what vulnerabilities exist:
# Scan a local image
docker pull python:3.11-slim
trivy image python:3.11-slim
Trivy will output a color-coded table showing:
- Library/package name and version
- CVE ID (e.g., CVE-2024-XXXXX)
- Severity (CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN)
- Fix version — the version that patches the vulnerability
- Status — whether a fix is available
Selecting Severity Thresholds
For CI/CD enforcement, you typically want to fail the pipeline only on CRITICAL and HIGH severities:
# Only show critical and high severity
trivy image --severity CRITICAL,HIGH python:3.11-slim
# Exit with non-zero code if any vulnerabilities found
trivy image --severity CRITICAL,HIGH --exit-code 1 python:3.11-slim
Warning: Setting
--exit-code 1 will cause the shell to report failure. In a pipeline, this stops execution. Use this intentionally — blocking on MEDIUM severity may be too aggressive for development environments.
Step 3: Scan Beyond Containers — IaC and Secrets
Trivy isn’t limited to container images. It can also scan Infrastructure-as-Code files, Kubernetes manifests, and detect hardcoded secrets.
# Scan IaC misconfigurations (Terraform, CloudFormation, etc.)
trivy config ./terraform/
# Scan Kubernetes manifests for security issues
trivy config ./k8s/deployment.yaml
# Scan filesystem for secrets (API keys, passwords)
trivy fs --scanners secret ./my-repo/
# Scan a remote Git repository
trivy repo https://github.com/org/repo.git
This makes Trivy a unified security scanner — one tool for images, configs, filesystems, and Git repos. No more cobbling together separate tools for each surface.
Step 4: Automate Scanning in GitHub Actions
This is where security becomes automated and enforceable. Here’s a complete GitHub Actions workflow that builds, scans, and conditionally pushes a Docker image:
name: Build and Security Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-and-scan:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # For SARIF upload
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build Docker 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'
exit-code: '1'
- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
category: 'container-image-scan'
- name: Push to registry (only if scan passed)
if: success()
run: |
docker tag myapp:${{ github.sha }} ghcr.io/org/myapp:latest
docker push ghcr.io/org/myapp:latest
Key Security Pattern: The
exit-code: '1' parameter makes Trivy return a non-zero exit code when CRITICAL or HIGH vulnerabilities are found. This causes the Push to registry step to be skipped because it depends on if: success(). Vulnerable images never reach production.
Step 5: Verification — Test the Security Gate
Let’s verify the pipeline works as expected by creating a deliberately vulnerable Dockerfile and running Trivy against it:
# Create a vulnerable Dockerfile
cat > Dockerfile.vulnerable << 'EOF'
FROM python:3.7-slim # Known EOL version with many CVEs
COPY app.py /app/
RUN pip install flask==1.0 # Old Flask with known vulns
CMD ["python", "/app/app.py"]
EOF
# Scan it — this should find CRITICAL/HIGH issues
trivy image --severity CRITICAL,HIGH --exit-code 1 \
--ignore-unfixed=false \
-f table docker.io/python:3.7-slim
If Trivy finds vulnerabilities, you’ll see:
- A non-zero exit code (pipeline will fail)
- A table of CVEs with severity, package, and fix version
- The image is blocked from registry push
Now test with a secure image:
# Use the latest slim Python image
trivy image --severity CRITICAL,HIGH --exit-code 1 python:3.12-slim
# If no CRITICAL/HIGH vulns found, exit code is 0 (PASS)
Verifying in GitHub Security Tab
After the SARIF upload step runs, navigate to your GitHub repository and go to Security → Code scanning alerts. You’ll see all the Trivy-discovered vulnerabilities integrated into GitHub’s native security interface, with:
- CVE descriptions and links
- Severity classification
- Affected package versions and fix suggestions
- Alert history and dismissal capabilities
Conclusion
Container image security scanning with Trivy is one of the highest-impact security measures you can adopt for cloud-native workloads. In this tutorial, you learned:
- What Trivy is and how it cross-references packages against multiple vulnerability databases
- How to install Trivy locally or use it as a Docker image
- How to scan container images, IaC files, and detect secrets
- How to automate scanning in GitHub Actions with a security gate
- How to verify the pipeline blocks vulnerable images
By integrating Trivy into your CI/CD pipelines, you shift security left — catching vulnerabilities before they reach a registry or production environment. This is a core tenet of DevSecOps: security is not a gate at the end, but a continuous process throughout the delivery lifecycle.
Next steps:
- Explore Trivy’s
.trivyignorefile to suppress false positives - Integrate with
Slack webhooks for real-time vulnerability alerts
- Add
Cosign image signing and verification alongside Trivy scanning
- Enable scheduled scans of your container registry using
scheduled workflows
— Published by Nova Tech Cloud Tutorials