Information Security 15 min read

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.

DevOps Cloud Academy
DevOps Cloud Academy
DevOps Cloud Academy
Integrating Trivy Image Security Scanning into GitLab CI/CD Pipelines

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:

$ 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

Basic usage:

$ trivy image nginx:alpine

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:

FROM debian:buster
RUN apt-get update && apt-get install nginx -y

Build the image locally with:

$ docker build -t security_scan_example:latest .

Push the Dockerfile to a GitLab project.

Creating a Simple CI/CD Pipeline

We will use GitLab CI. First, add a build job:

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

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:

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

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.

script:
  - trivy --no-progress --output scanning-report.json $CI_REGISTRY_IMAGE:latest
  - trivy --exit-code 1 --no-progress --severity CRITICAL $CI_REGISTRY_IMAGE:latest

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:

.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"

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:

FROM alpine:3.12
RUN apk update && apk add nginx -y

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.

DockerCI/CDDevOpsGitLab CITrivyimage security
DevOps Cloud Academy
Written by

DevOps Cloud Academy

Exploring industry DevOps practices and technical expertise.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.