---
title: "Container Image Vulnerability Scanning in CI/CD with Trivy"
lang: "en"
author: "Mohammad Abu Mattar"
canonical: https://mkabumattar.com/post/container-image-vulnerability-scanning-trivy
---

![Blog post image for Container Image Vulnerability Scanning in CI/CD with Trivy - How to automate container image vulnerability scanning in CI/CD with Trivy: installation, severity thresholds, GitHub Actions and GitLab CI integration, policy enforcement, and remediation workflows.](/_astro/hero.yY1orHlw_Z2wLbeG.webp)

[Home](/)›[Devtips](/devtips)›[All Categories](/devtips/categories)›[DevOps & DevSecOps](/devtips/categories/devops--devsecops)

Devtips

[Prev in DevOps & DevSecOps7 Reasons Learning the Linux Terminal is Worth It (Even for Beginners)](/devtips/post/7-reasons-learning-linux-terminal-worth-it-beginners)[Next in DevOps & DevSecOpsGitHub Actions Secrets and Environment Variables: Handle Config the Right Way](/devtips/post/github-actions-secrets-environment-variables-guide)

[DevOps & DevSecOps](/devtips/categories/devops--devsecops)

# Container Image Vulnerability Scanning in CI/CD with Trivy

[Mohammad Abu Mattar](/authors/mohammad-abu-mattar)Published: 10 Mar 202604 Mins read04 Mins listen

[Markdown for AI(opens in a new tab)](/post/container-image-vulnerability-scanning-trivy/index.md "Open the plain-Markdown version of this page, for pasting into an AI tool")

TL;DR

How to automate container image vulnerability scanning in CI/CD with Trivy: installation, severity thresholds, GitHub Actions and GitLab CI integration, policy enforcement, and remediation workflows.

Series

[Container Security & DevSecOps](/series/container-security--devsecops)1/1

All posts in this series (1)

DevTips1

1.  [Container Image Vulnerability Scanning in CI/CD with TrivyYou are here](/devtips/post/container-image-vulnerability-scanning-trivy)

### Container Image Vulnerability Scanning in CI/CD with Trivy

Contents

[Why container security matters](#why-container-security-matters)[Where the vulnerabilities hide](#where-the-vulnerabilities-hide)[The numbers](#the-numbers)[The challenge](#the-challenge)[Why manual review isn't enough](#why-manual-review-isnt-enough)[Common vulnerabilities in containers](#common-vulnerabilities-in-containers)[The fix: Trivy](#the-fix-trivy)[What Trivy is](#what-trivy-is)[Why I reach for Trivy](#why-i-reach-for-trivy)[Installation and setup](#installation-and-setup)[Installing Trivy](#installing-trivy)[Basic image scanning](#basic-image-scanning)[Setting severity thresholds](#setting-severity-thresholds)[Scanning at specific severity levels](#scanning-at-specific-severity-levels)[Output formats](#output-formats)[CI/CD integration](#cicd-integration)[GitHub Actions workflow](#github-actions-workflow)[GitLab CI](#gitlab-ci)[Policy enforcement](#policy-enforcement)[Creating a Trivy policy](#creating-a-trivy-policy)[Ignoring false positives](#ignoring-false-positives)[Beyond images](#beyond-images)[Scanning filesystems](#scanning-filesystems)[Scanning configuration files](#scanning-configuration-files)[Generating a Software Bill of Materials (SBOM)](#generating-a-software-bill-of-materials-sbom)[Remediation](#remediation)[When vulnerabilities are found](#when-vulnerabilities-are-found)[A scheduled scan across every image](#a-scheduled-scan-across-every-image)[Monitoring and reporting](#monitoring-and-reporting)[Storing results over time](#storing-results-over-time)[Tracking vulnerability trends](#tracking-vulnerability-trends)[Best practices](#best-practices)[1\. Scan early and often](#1-scan-early-and-often)[2\. Use minimal base images](#2-use-minimal-base-images)[3\. Update dependencies regularly](#3-update-dependencies-regularly)[4\. Keep SBOMs](#4-keep-sboms)[Registry integration](#registry-integration)[Push only images that passed](#push-only-images-that-passed)[Wrapping up](#wrapping-up)[Resources](#resources)

## [Why container security matters](#why-container-security-matters)

### [Where the vulnerabilities hide](#where-the-vulnerabilities-hide)

**A container image is one of the largest pieces of untrusted code you ship.**

Every image you build carries the base OS layer, runtime libraries, your dependencies and your application code. Any of those layers can hold known CVEs, and most of them you didn’t write. Without scanning, that whole stack reaches production unread.

### [The numbers](#the-numbers)

-   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 challenge](#the-challenge)

### [Why manual review isn’t enough](#why-manual-review-isnt-enough)

Nobody is going to read the dependency tree of every image on every build. Without automation, a vulnerable image ships, and you find out about it from a CVE feed or an incident rather than from the pipeline that built it.

### [Common vulnerabilities in containers](#common-vulnerabilities-in-containers)

-   **Outdated base images** with unpatched OS vulnerabilities
-   **Vulnerable dependencies** pulled in from npm, pip or Maven
-   **Exposed secrets** accidentally included in image layers
-   **Misconfigurations** creating insecure defaults
-   **Malware** hidden in supply chain attacks

## [The fix: Trivy](#the-fix-trivy)

### [What Trivy is](#what-trivy-is)

Trivy is a fast container vulnerability scanner from Aqua Security. It scans container images, filesystems and configuration files for known vulnerabilities, misconfigurations and secrets.

### [Why I reach for Trivy](#why-i-reach-for-trivy)

-   **Speed**: Scans images in seconds, not minutes
-   **Accuracy**: Supports multiple vulnerability databases (NVD, GitHub Security, Aqua, Alpine)
-   **Broad coverage**: 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](#installation-and-setup)

### [Installing Trivy](#installing-trivy)

Terminal window

```
# 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/trivy
```

### [Basic image scanning](#basic-image-scanning)

Terminal window

```
# Scan a local imagetrivy image my-app:latest
# Scan from registrytrivy image nginx:latest
# Scan with detailed outputtrivy image --severity HIGH,CRITICAL my-app:latest
```

## [Setting severity thresholds](#setting-severity-thresholds)

### [Scanning at specific severity levels](#scanning-at-specific-severity-levels)

Terminal window

```
# 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:latest
```

### [Output formats](#output-formats)

Terminal window

```
# 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:latest
```

## [CI/CD integration](#cicd-integration)

### [GitHub Actions workflow](#github-actions-workflow)

.github/workflows/container-scan.yml

```
1name: Container Vulnerability Scan2
3on:4  push:5    branches: [main]6    paths:7      - 'Dockerfile'8      - 'src/**'9  pull_request:10    branches: [main]11
12jobs:13  trivy-scan:14    runs-on: ubuntu-latest15    steps:16      - uses: actions/checkout@v317
18      - name: Set up Docker Buildx19        uses: docker/setup-buildx-action@v220
21      - name: Build Docker image22        uses: docker/build-push-action@v423        with:24          context: .25          file: ./Dockerfile26          push: false27          load: true28          tags: my-app:${{ github.sha }}29
30      - name: Run Trivy vulnerability scan31        uses: aquasecurity/trivy-action@master32        with:33          image-ref: my-app:${{ github.sha }}34          format: 'sarif'35          output: 'trivy-results.sarif'36          severity: 'CRITICAL,HIGH'37
38      - name: Upload Trivy results to GitHub Security39        uses: github/codeql-action/upload-sarif@v240        with:41          sarif_file: 'trivy-results.sarif'42
43      - name: Fail if critical vulnerabilities found44        run: |45          trivy image --severity CRITICAL my-app:${{ github.sha }} --exit-code 1
```

### [GitLab CI](#gitlab-ci)

.gitlab-ci.yml

```
1stages:2  - build3  - scan4
5build:6  stage: build7  image: docker:latest8  services:9    - docker:dind10  script:11    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .12    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA13
14scan:15  stage: scan16  image: aquasec/trivy:latest17  script:18    - trivy image --severity HIGH,CRITICAL --exit-code 1 $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA19  allow_failure: false
```

## [Policy enforcement](#policy-enforcement)

### [Creating a Trivy policy](#creating-a-trivy-policy)

trivy-policy.yaml

```
1# Define what constitutes a vulnerability violation2severity: HIGH,CRITICAL3
4# Ignore specific CVEs for known/accepted risks5ignorefile: .trivyignore6
7# Policy for failing builds8exit-code: 19
10# Require sign-off for medium severity11medium-requires-approval: true
```

### [Ignoring false positives](#ignoring-false-positives)

.trivyignore

```
# 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-5678
```

## [Beyond images](#beyond-images)

### [Scanning filesystems](#scanning-filesystems)

Terminal window

```
# Scan local directorytrivy fs .
# Scan with detailed outputtrivy fs --severity HIGH,CRITICAL --format json . > fs-scan.json
```

### [Scanning configuration files](#scanning-configuration-files)

Terminal window

```
# Detect misconfigurations in Dockerfiletrivy config Dockerfile
# Scan Kubernetes manifeststrivy config k8s-manifests/
```

### [Generating a Software Bill of Materials (SBOM)](#generating-a-software-bill-of-materials-sbom)

Terminal window

```
# 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.spdx
```

## [Remediation](#remediation)

### [When vulnerabilities are found](#when-vulnerabilities-are-found)

1.  **Smallest change**: update the base image

```
1# Before2FROM ubuntu:20.043
4# After5FROM ubuntu:22.04
```

2.  **Targeted change**: update the vulnerable dependency

```
1FROM node:18-alpine2
3# Install with security patches4RUN npm install --no-save my-package@latest
```

3.  **Last resort**: rebuild the image without cache

Terminal window

```
docker build --no-cache -t my-app:latest .
```

## [A scheduled scan across every image](#a-scheduled-scan-across-every-image)

.github/workflows/production-scan.yml

```
1name: Production Container Security2
3on:4  schedule:5    # Run daily scans6    - cron: '0 2 * * *'7  workflow_dispatch:8
9jobs:10  scan-all-images:11    runs-on: ubuntu-latest12    strategy:13      matrix:14        image:15          - my-app:latest16          - api-gateway:latest17          - worker-service:latest18
19    steps:20      - uses: aquasecurity/trivy-action@master21        with:22          image-ref: ${{ matrix.image }}23          format: 'json'24          output: 'trivy-${{ matrix.image }}.json'25          severity: 'CRITICAL,HIGH,MEDIUM'26
27      - name: Archive results28        uses: actions/upload-artifact@v329        with:30          name: trivy-reports31          path: trivy-*.json32
33      - name: Notify security team34        if: failure()35        run: |36          curl -X POST -H 'Content-type: application/json' \37            --data '{"text":"Critical vulnerabilities found in ${{ matrix.image }}"}' \38            ${{ secrets.SLACK_WEBHOOK_URL }}
```

## [Monitoring and reporting](#monitoring-and-reporting)

### [Storing results over time](#storing-results-over-time)

Terminal window

```
# Generate timestamped reportsTIMESTAMP=$(date +%Y%m%d-%H%M%S)trivy image --format json my-app:latest > reports/scan-$TIMESTAMP.json
```

### [Tracking vulnerability trends](#tracking-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](#best-practices)

### [1\. Scan early and often](#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](#2-use-minimal-base-images)

```
1# Reduce attack surface2FROM alpine:3.18 as base3FROM gcr.io/distroless/base-debian11
```

### [3\. Update dependencies regularly](#3-update-dependencies-regularly)

Terminal window

```
# Update dependencies regularlynpm audit fix --forcepython -m pip install --upgrade pip
```

### [4\. Keep SBOMs](#4-keep-sboms)

Generate and store SBOMs for supply chain transparency:

Terminal window

```
trivy image --format cyclonedx my-app:latest > sbom.jsongit add sbom.jsongit commit -m "Update SBOM for security tracking"
```

## [Registry integration](#registry-integration)

### [Push only images that passed](#push-only-images-that-passed)

Terminal window

```
# Only push if scan passestrivy image --severity CRITICAL,HIGH --exit-code 1 my-app:latest && \  docker push my-registry/my-app:latest
```

## [Wrapping up](#wrapping-up)

**If you ship containers, something has to scan them before the registry does.**

Trivy is the cheapest way I know to do that. Add it to the pipeline, pick the severity you’ll fail the build on, and treat the `.trivyignore` file as something that gets reviewed rather than something that grows. Start at CRITICAL if HIGH would block every build on day one, then tighten it once the base images are current.

## [Resources](#resources)

-   [Trivy Official Documentation](https://aquasecurity.github.io/trivy)
-   [GitHub Container Scanning Action](https://github.com/aquasecurity/trivy-action)
-   [CVE Database References](https://nvd.nist.gov)
-   [Distroless Images](https://github.com/GoogleContainerTools/distroless)

Was this useful?

## Tags

[#Container Security](/devtips/tags/container-security)[#Vulnerability Scanning](/devtips/tags/vulnerability-scanning)[#Trivy](/devtips/tags/trivy)[#Docker](/devtips/tags/docker)[#Supply Chain Security](/devtips/tags/supply-chain-security)[#CI/CD](/devtips/tags/cicd)[#DevSecOps](/devtips/tags/devsecops)

## Share

[Facebook](https://facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmkabumattar.com%2Fdevtips%2Fpost%2Fcontainer-image-vulnerability-scanning-trivy "Share on Facebook")[Twitter](https://twitter.com/intent/tweet/?text=Container%20Image%20Vulnerability%20Scanning%20in%20CI%2FCD%20with%20Trivy&url=https%3A%2F%2Fmkabumattar.com%2Fdevtips%2Fpost%2Fcontainer-image-vulnerability-scanning-trivy "Share on Twitter")[LinkedIn](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fmkabumattar.com%2Fdevtips%2Fpost%2Fcontainer-image-vulnerability-scanning-trivy&title=Container%20Image%20Vulnerability%20Scanning%20in%20CI%2FCD%20with%20Trivy&summary=How%20to%20automate%20container%20image%20vulnerability%20scanning%20in%20CI%2FCD%20with%20Trivy%3A%20installation%2C%20severity%20thresholds%2C%20GitHub%20Actions%20and%20GitLab%20CI%20integration%2C%20policy%20enforcement%2C%20and%20remediation%20workflows.&source=https://mkabumattar.com "Share on LinkedIn")[WhatsApp](https://wa.me/?text=Container%20Image%20Vulnerability%20Scanning%20in%20CI%2FCD%20with%20Trivy%20https%3A%2F%2Fmkabumattar.com%2Fdevtips%2Fpost%2Fcontainer-image-vulnerability-scanning-trivy "Share on WhatsApp")[Telegram](https://t.me/share/url?url=https%3A%2F%2Fmkabumattar.com%2Fdevtips%2Fpost%2Fcontainer-image-vulnerability-scanning-trivy&text=Container%20Image%20Vulnerability%20Scanning%20in%20CI%2FCD%20with%20Trivy "Share on Telegram")[Reddit](https://www.reddit.com/submit?url=https%3A%2F%2Fmkabumattar.com%2Fdevtips%2Fpost%2Fcontainer-image-vulnerability-scanning-trivy&title=Container%20Image%20Vulnerability%20Scanning%20in%20CI%2FCD%20with%20Trivy "Share on Reddit")[Hacker News](http://news.ycombinator.com/submitlink?u=https%3A%2F%2Fmkabumattar.com%2Fdevtips%2Fpost%2Fcontainer-image-vulnerability-scanning-trivy&t=Container%20Image%20Vulnerability%20Scanning%20in%20CI%2FCD%20with%20Trivy "Share on Hacker News")[Pinterest](https://pinterest.com/pin/create/button/?url=https%3A%2F%2Fmkabumattar.com%2Fdevtips%2Fpost%2Fcontainer-image-vulnerability-scanning-trivy&media=&description=How%20to%20automate%20container%20image%20vulnerability%20scanning%20in%20CI%2FCD%20with%20Trivy%3A%20installation%2C%20severity%20thresholds%2C%20GitHub%20Actions%20and%20GitLab%20CI%20integration%2C%20policy%20enforcement%2C%20and%20remediation%20workflows. "Share on Pinterest")[Email](<mailto:?subject=Container%20Image%20Vulnerability%20Scanning%20in%20CI%2FCD%20with%20Trivy&body=Check out this article: https%3A%2F%2Fmkabumattar.com%2Fdevtips%2Fpost%2Fcontainer-image-vulnerability-scanning-trivy>)

## Comments

## You might also enjoy

More posts on similar topics

[![Securing CI/CD with IAM Roles](/_astro/hero.Bl9B2DZz_ZDIuXQ.webp)](/devtips/post/securing-cicd-with-iam-roles)

## [Securing CI/CD with IAM Roles](/devtips/post/securing-cicd-with-iam-roles)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [DevOps & DevSecOps](/devtips/categories/devops--devsecops)

Why secure your CI/CD pipeline? Why pipeline security matters Your pipeline holds credentials for every environment you deploy to, which makes it one of the most valuable targets you own. A s

[#CICD Security](/devtips/tags/cicd-security)[#IAM Roles](/devtips/tags/iam-roles)[#Least Privilege](/devtips/tags/least-privilege)+4 tags

[read more](/devtips/post/securing-cicd-with-iam-roles)

[![Policy-as-Code Governance with OPA/Rego](/_astro/hero.CwAJ64Mi_1WeH9p.webp)](/devtips/post/policy-as-code-opa-rego)

## [Policy-as-Code Governance with OPA/Rego](/devtips/post/policy-as-code-opa-rego)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [DevOps & DevSecOps](/devtips/categories/devops--devsecops)

Why policy-as-code matters The governance problem Managing infrastructure at scale gets complicated fast. As your infrastructure grows, keeping it consistent and compliant gets harder. M

[#Policy as Code](/devtips/tags/policy-as-code)[#OPA/Rego](/devtips/tags/oparego)[#Compliance](/devtips/tags/compliance)+4 tags

[read more](/devtips/post/policy-as-code-opa-rego)

[![GitHub Actions Secrets and Environment Variables: Handle Config the Right Way](/_astro/hero.DSfz34Ly_ZH1GxC.webp)](/devtips/post/github-actions-secrets-environment-variables-guide)

## [GitHub Actions Secrets and Environment Variables: Handle Config the Right Way](/devtips/post/github-actions-secrets-environment-variables-guide)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [DevOps & DevSecOps](/devtips/categories/devops--devsecops)

Why secrets handling matters Most CI leaks are config mistakes, not attacks Hey, want to stop leaking credentials in your pipelines? Most secret leaks in CI are not the result of some cle

[#GitHub Actions](/devtips/tags/github-actions)[#Secrets](/devtips/tags/secrets)[#Environment Variables](/devtips/tags/environment-variables)+4 tags

[read more](/devtips/post/github-actions-secrets-environment-variables-guide)

[![7 Reasons Learning the Linux Terminal is Worth It (Even for Beginners)](/_astro/hero.Ci9C_A6W_1AKnQ0.webp)](/devtips/post/7-reasons-learning-linux-terminal-worth-it-beginners)

## [7 Reasons Learning the Linux Terminal is Worth It (Even for Beginners)](/devtips/post/7-reasons-learning-linux-terminal-worth-it-beginners)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [DevOps & DevSecOps](/devtips/categories/devops--devsecops)

Why learn the Linux terminal? Why it still matters Even with the graphical tools and AI assistants available now, the terminal is the most direct way to work with a Linux system. It's a core

[#Linux](/devtips/tags/linux)[#Terminal](/devtips/tags/terminal)[#Command Line](/devtips/tags/command-line)+4 tags

[read more](/devtips/post/7-reasons-learning-linux-terminal-worth-it-beginners)

[![Docker Is Eating Your Disk Space (And How PruneMate Fixes It)](/_astro/hero.BD8-gtdG_26qrXW.webp)](/devtips/post/docker-disk-space-prunemate)

## [Docker Is Eating Your Disk Space (And How PruneMate Fixes It)](/devtips/post/docker-disk-space-prunemate)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [Containers & Docker](/devtips/categories/containers--docker)

The problem: Docker is eating your disk space What it looks like when it happens Your Docker host is running out of space. Again. You've been spinning up containers, testing new services

[#Docker](/devtips/tags/docker)[#Containers](/devtips/tags/containers)[#Home Lab](/devtips/tags/home-lab)+5 tags

[read more](/devtips/post/docker-disk-space-prunemate)

[![Kubernetes Namespaces: Organize, Isolate, and Secure Multi-Team Clusters](/_astro/hero.DwnjQnvN_Z1FaY8R.webp)](/devtips/post/kubernetes-namespaces-organize-isolate-multi-team)

## [Kubernetes Namespaces: Organize, Isolate, and Secure Multi-Team Clusters](/devtips/post/kubernetes-namespaces-organize-isolate-multi-team)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [Kubernetes & Cloud Native](/devtips/categories/kubernetes--cloud-native)

Why cluster isolation matters The multi-tenant reality If you're running a separate cluster for every environment and every dev team, you have already seen the bill and the amount of upgrade

[#Kubernetes](/devtips/tags/kubernetes)[#Namespaces](/devtips/tags/namespaces)[#Multi Tenancy](/devtips/tags/multi-tenancy)+6 tags

[read more](/devtips/post/kubernetes-namespaces-organize-isolate-multi-team)

6 related posts
