Integrating Trivy Image Security Scanning into GitLab CI/CD Pipelines
This tutorial demonstrates how to integrate Trivy image security scanning into a GitLab CI/CD pipeline, covering tool selection, Dockerfile creation, pipeline configuration, scheduled scans, handling vulnerability reports, and strategies for failing builds based on severity levels.
Using GitLab CI and Trivy
Introduction
Image security scanning has become increasingly popular. The idea is to analyze a Docker image and look for vulnerabilities based on a CVE database, allowing you to know which vulnerabilities are present before using the image in production.
There are several ways to scan Docker images depending on the tool you use. You can run a scan from the CLI, integrate it directly into a container registry, or, preferably, embed the scan into a CI/CD pipeline. The latter automates the process and continuously analyzes generated images, aligning with DevOps principles.
Below is a simple example:
Today I will show you how to set up image security scanning integrated into a CI/CD pipeline.
Tools
Several tools can perform image security scanning:
Trivy – developed by Aqua Security.
Anchore – developed by Anchore Inc.
Clair – developed by Quay.
Docker Trusted Registry – built‑in scanning for Docker Enterprise.
Cloud providers (Azure/AWS/GCP) – often offer scanning as a service.
For this tutorial we will use Trivy on a GitLab CI pipeline.
Trivy Quick Overview
Trivy is an easy‑to‑use yet accurate image scanner. Installation is straightforward:
<code style="padding: 16px; color: #abb2bf; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px">$ curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/master/contrib/install.sh | sh -s --b /usr/local/bin
$ sudo mv ./bin/trivy /usr/local/bin/trivy
$ trivy --version</code>Basic usage:
<code style="padding: 16px; color: #abb2bf; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px">$ trivy image nginx:alpine</code>The command produces output similar to the screenshot below:
Adding a Simple Docker Image
We need a Docker image to demonstrate the scan. Here is a minimal Dockerfile:
<code style="padding: 16px; color: #abb2bf; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px">FROM debian:buster
RUN apt-get update && apt-get install nginx -y</code>Build the image locally with:
<code style="padding: 16px; color: #abb2bf; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px">$ docker build -t security_scan_example:latest .</code>Push the Dockerfile to a GitLab project.
Creating a Simple CI/CD Pipeline
We will use GitLab CI. First, add a build job:
<code style="padding: 16px; color: #abb2bf; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px">build:
stage: build
image: docker:stable
services:
- docker:dind
tags:
- docker
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- docker build -t $CI_REGISTRY_IMAGE:latest .
- docker push $CI_REGISTRY_IMAGE:latest</code>The job runs on a docker:stable container, builds the image from the Dockerfile, and pushes it to the GitLab container registry.
Next, add the security scan job:
<code style="padding: 16px; color: #abb2bf; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px">security_scan:
stage: test
image:
name: aquasec/trivy:latest
entrypoint: [""]
services:
- docker:dind
tags:
- docker
script:
- trivy --no-progress --output scanning-report.txt $CI_REGISTRY_IMAGE:latest
artifacts:
reports:
container_scanning: scanning-report.txt</code>This job runs Trivy inside the official Trivy image, scans the built image, and stores the report as a pipeline artifact.
After pushing code, both jobs run automatically, as shown in the pipeline view screenshots below:
The security scan job produces a report with 114 low, 8 medium, 24 high, and 1 critical vulnerability.
Where is the Report?
The report is saved as an artifact and can be downloaded from the job details page. After downloading, you can view detailed information such as affected libraries, CVE IDs, severity, and possible fixes.
What to Do Next?
By default Trivy exits with code 0, so the pipeline never fails even if vulnerabilities are found. To fail the pipeline on critical issues, use Trivy’s --severity and --exit-code options.
<code style="padding: 16px; color: #abb2bf; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px">script:
- trivy --no-progress --output scanning-report.json $CI_REGISTRY_IMAGE:latest
- trivy --exit-code 1 --no-progress --severity CRITICAL $CI_REGISTRY_IMAGE:latest</code>Now the job will succeed only if no critical vulnerabilities are detected.
Final Step…
So far the scan runs only when the image is built/pushed. Because new CVEs appear daily, we add a scheduled pipeline (e.g., every night at 2 AM) that runs only the security_scan job.
Create a variable SCHEDULED_PIPELINE with value security_scan in the schedule, then adjust the .gitlab-ci.yml to use a template and conditional execution:
<code style="padding: 16px; color: #abb2bf; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px">.scanning-template: &scanning-template
stage: test
image:
name: aquasec/trivy:latest
entrypoint: [""]
services:
- docker:dind
tags:
- docker
script:
- trivy --no-progress --output scanning-report.json $CI_REGISTRY_IMAGE:latest
- trivy --exit-code 1 --no-progress --severity CRITICAL $CI_REGISTRY_IMAGE:latest
artifacts:
reports:
container_scanning: scanning-report.json
build:
stage: build
image: docker:stable
services:
- docker:dind
tags:
- docker
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- docker build -t $CI_REGISTRY_IMAGE:latest .
- docker push $CI_REGISTRY_IMAGE:latest
except:
variables:
- $SCHEDULED_PIPELINE
security_scan:
<<: *scanning-template
except:
variables:
- $SCHEDULED_PIPELINE
security_scan:on-schedule:
<<: *scanning-template
only:
variables:
- $SCHEDULED_PIPELINE == "security_scan"</code>Now normal pushes trigger both build and scan, while the scheduled pipeline runs only the scan at the defined time.
How to Fix These Vulnerabilities?
Typically you upgrade the base image or the vulnerable packages (e.g., upgrade nginx). Another approach is to minimize the image by removing unnecessary components.
For example, switch to an Alpine base image:
<code style="padding: 16px; color: #abb2bf; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px">FROM alpine:3.12
RUN apk update && apk add nginx -y</code>After updating the Dockerfile, the pipeline runs successfully with zero reported vulnerabilities.
Conclusion
We have shown how easy it is to integrate a security scanning job into a GitLab CI pipeline using Trivy. In real‑world projects with multiple branches, additional configuration may be required, but the core concept remains the same.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
DevOps Cloud Academy
Exploring industry DevOps practices and technical expertise.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
