Scan Docker Images for CVEs with Trivy: A CI/CD Security Guide

Security tutorial - IT technology blog
Security tutorial - IT technology blog

Securing Your Containers Before They Reach Production

Most developers treat Docker images like black boxes. You pick a base image, install your dependencies, and push it to a registry. However, that base image might contain hundreds of known vulnerabilities (CVEs) before you even write your first line of code. If one of those vulnerabilities allows remote code execution, your application is at risk from the moment it goes live.

After my server got hit by SSH brute-force attacks at midnight, I always prioritize security from initial setup. That experience taught me that hackers don’t wait for you to be ready; they look for any open door. A vulnerable library inside your Docker container is exactly the kind of door they love. This is where Trivy comes in—it acts as a gatekeeper, ensuring that only “clean” images make it to your production environment.

Comparing Approaches to Image Scanning

There are several ways to handle container security. Choosing the right one depends on your workflow and the level of control you need.

1. Manual Scanning

This involves running a scan on your local machine before pushing an image. While better than nothing, it relies on human memory. If a developer forgets to scan, a vulnerable image slips through.

2. Registry-Side Scanning

Platforms like Docker Hub, AWS ECR, or GitHub Container Registry often have built-in scanners. These are great because they happen automatically, but they occur after the image is already pushed. If a vulnerability is found, you have to go back, fix it, and push again.

3. CI/CD Pipeline Scanning (The Shift-Left Approach)

This is the most effective method. By integrating a tool like Trivy directly into your CI/CD pipeline, the build fails if the image contains critical vulnerabilities. You catch the problem before the image ever leaves the build server. This “shifts” security to the left (earlier in the development process).

Pros and Cons of Using Trivy

Trivy has quickly become the industry standard for open-source vulnerability scanning. Here is why it stands out, along with a few trade-offs.

The Pros

  • Fast and Lightweight: Trivy doesn’t require a heavy background daemon or a complex database setup. It downloads a small metadata database the first time you run it and stays updated incrementally.
  • Comprehensive: It doesn’t just scan OS packages (like apt or yum); it also scans language-specific dependencies (Python, Node.js, Go, etc.) and even looks for misconfigured Dockerfiles.
  • Easy Integration: Because it is a single binary, you can run it in any Linux environment, including GitHub Actions, GitLab CI, and Jenkins.
  • No Account Required: Unlike some commercial tools, you don’t need to sign up or provide an API key to start scanning.

The Cons

  • False Positives: Like any scanner, Trivy might occasionally flag a vulnerability that doesn’t actually affect your specific configuration. You have to learn how to use .trivyignore files to manage these.
  • Resource Usage: Scanning very large images with thousands of dependencies can be CPU-intensive for small CI runners.

Recommended Setup for Junior Developers

If you are just starting out, the best setup is to use Trivy locally for testing and then add a simple GitHub Action to your repository. This ensures that every Pull Request is checked for security flaws before it is merged.

For your local machine, you can install Trivy using a simple script or a package manager. On Ubuntu/Debian, it looks like this:

sudo apt-get install wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/dabest/public.key | sudo apt-key add -
echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt-get update
sudo apt-get install trivy

Step-by-Step Implementation Guide

Step 1: Running Your First Local Scan

Pick an image you use frequently. For example, let’s look at the official Python image. Run the following command in your terminal:

trivy image python:3.9-slim

Trivy will output a table showing the Library, the CVE ID, the Severity (Low, Medium, High, Critical), and whether a fix is available. Seeing a long list of “Critical” vulnerabilities in a standard base image is usually a wake-up call for most developers.

Step 2: Filtering Results

You usually don’t want to see “Low” or “Medium” vulnerabilities during a quick check. You can filter the output to focus only on what matters:

trivy image --severity HIGH,CRITICAL python:3.9-slim

If you only want to see vulnerabilities that actually have a fix available (so you don’t waste time on unpatchable issues), use this:

trivy image --ignore-unfixed --severity HIGH,CRITICAL python:3.9-slim

Step 3: Integrating into GitHub Actions

This is where the magic happens. We want our CI pipeline to break if a critical vulnerability is found. Create a file at .github/workflows/security.yml in your project:

name: Security Scan
on: [push, pull_request]

jobs:
  trivy-scan:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Build Docker image
        run: docker build -t my-app:${{ github.sha }} .

      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'my-app:${{ github.sha }}'
          format: 'table'
          exit-code: '1' # This fails the build if vulnerabilities are found
          ignore-unfixed: true
          severity: 'CRITICAL,HIGH'

In this configuration, exit-code: '1' is the most important part. It tells the CI runner that if Trivy finds a HIGH or CRITICAL vulnerability, it should return a failure status. This stops the pipeline, preventing the image from being deployed to production.

Step 4: Handling False Positives with .trivyignore

Sometimes you might find a vulnerability that you know is not exploitable in your environment. Instead of ignoring all warnings, create a .trivyignore file in your root directory and add the CVE IDs you want to skip:

# Ignore specific CVE that doesn't affect our app
CVE-2023-12345
# Ignore another one with a known workaround
CVE-2024-99999

Trivy will now skip these during the scan, keeping your CI green while still protecting you from everything else.

Final Thoughts

Setting up Trivy takes less than 15 minutes, but it can save you from a catastrophic security breach. Since I started using it, I’ve caught dozens of outdated base images that I previously thought were safe. Security isn’t a one-time task; it’s a habit. By making scanning part of your automated workflow, you ensure that your production environment remains a fortress, even when you’re asleep.

Share: