← Blog / DevSecOps

Trivy in CI/CD Pipeline — Automatic Docker Image Scanning

Introduction

A vulnerable library in a Docker image is a time bomb. It can reach production unnoticed and wait for months — until someone exploits it. The solution is automatic scanning of every image before deployment. Trivy does it in tens of seconds and integrates with every popular CI/CD system.


What Does Trivy Scan?

Trivy scans:
├── Docker Images
│   ├── Vulnerabilities in system packages (apt, rpm, apk)
│   ├── Vulnerabilities in application libraries (pip, npm, gem, go)
│   └── Hard-coded secrets and passwords
├── Kubernetes configuration files
│   ├── Missing resource limits
│   ├── Containers running as root
│   └── Privileged containers
└── Git repositories
    └── Secrets in commit history

GitLab CI Integration

Basic integration — blocking the pipeline

variables:
  IMAGE_NAME: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

stages:
  - build
  - security
  - deploy

build-image:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t $IMAGE_NAME .
    - docker push $IMAGE_NAME

trivy-scan:
  stage: security
  image:
    name: aquasec/trivy:0.48.3
    entrypoint: [""]
  variables:
    TRIVY_USERNAME: $CI_REGISTRY_USER
    TRIVY_PASSWORD: $CI_REGISTRY_PASSWORD
    TRIVY_AUTH_URL: $CI_REGISTRY
    TRIVY_CACHE_DIR: .trivy-cache
  cache:
    paths:
      - .trivy-cache/
  script:
    - trivy image
        --exit-code 1
        --severity CRITICAL,HIGH
        --no-progress
        $IMAGE_NAME
  allow_failure: false

deploy-production:
  stage: deploy
  needs:
    - trivy-scan
  script:
    - echo "Deploy only if trivy-scan passed"

Extended integration with reports

trivy-scan-full:
  script:
    - trivy image
        --format template
        --template "@/contrib/gitlab.tpl"
        --output gl-container-scanning-report.json
        $IMAGE_NAME

    - trivy image
        --format template
        --template "@/contrib/html.tpl"
        --output trivy-report.html
        $IMAGE_NAME

  artifacts:
    when: always
    reports:
      container_scanning: gl-container-scanning-report.json
    paths:
      - trivy-report.html

GitHub Actions Integration

name: Security Scan

on:
  push:
    branches: [ main, develop ]

jobs:
  trivy-scan:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write

    steps:
      - uses: actions/checkout@v4

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

      - name: Run Trivy vulnerability scan
        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 SARIF to GitHub Security
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: trivy-results.sarif

Secret Detection

trivy fs --scanners secret .

# In pipeline:
trivy-secret-scan:
  script:
    - trivy fs --scanners secret --exit-code 1 --severity CRITICAL,HIGH .

Exceptions — Ignoring False Positives

# .trivyignore
CVE-2023-12345  # false positive — library is not used
CVE-2023-67890  # waiting for upstream fix

Severity Thresholds Policy

SeverityPipeline action
CRITICALBlock — no deployment
HIGHBlock (recommended)
MEDIUMWarning
LOWInformation in reports

Summary

Integrating Trivy with a pipeline is one of the best returns on investment in security — a dozen lines of configuration eliminates an entire class of vulnerabilities. Key principles:

  • Scan every image before deployment
  • Block pipeline on CRITICAL — always
  • Use cache for the vulnerability database — speeds scans from ~2min to ~15s
  • Integrate reports with GitLab Security Dashboard or GitHub Security