Trivy is surprisingly good at finding vulnerabilities, but its real power in GitLab CI comes from how it integrates with the platform’s security scanning features.
Let’s see Trivy in action, scanning a container image directly within a GitLab CI pipeline.
stages:
- build
- scan
- deploy
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
build_image:
stage: build
image: docker:20.10.16
services:
- docker:20.10.16-dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
scan_image:
stage: scan
image: aquasec/trivy:latest
script:
- trivy image --severity HIGH,CRITICAL --format template --template "@/contrib/gitlab.tpl" -o gl-container-scanning-report.json $IMAGE_TAG
artifacts:
reports:
container_scanning: gl-container-scanning-report.json
deploy_app:
stage: deploy
image: alpine:latest
script:
- echo "Deploying $IMAGE_TAG..."
# Add your deployment commands here
when: on_success # Only deploy if scanning passed (or was skipped)
In this .gitlab-ci.yml file, we have a standard pipeline: build a Docker image, scan it with Trivy, and then deploy. The scan_image stage is where the magic happens. We use the official aquasec/trivy Docker image. The core command is trivy image --severity HIGH,CRITICAL --format template --template "@/contrib/gitlab.tpl" -o gl-container-scanning-report.json $IMAGE_TAG.
This command tells Trivy to scan the $IMAGE_TAG (which is our built container image). We filter for HIGH and CRITICAL vulnerabilities. Crucially, --format template --template "@/contrib/gitlab.tpl" instructs Trivy to output the results in a format that GitLab understands for its security reports. The output is saved to gl-container-scanning-report.json, which is then declared as an artifact of type container_scanning. GitLab automatically picks this up and displays the vulnerabilities directly on merge requests and in the project’s security dashboard.
The problem this solves is making container security a first-class citizen in your CI/CD workflow. Instead of just building and deploying, you’re actively checking for known vulnerabilities in your container images before they reach production. This shifts security left, making it cheaper and easier to fix issues.
Internally, Trivy works by downloading vulnerability databases from various sources (like NVD, Red Hat, Debian, etc.) and comparing the installed packages and dependencies within your container image against these databases. It understands different package managers (apt, apk, yum, npm, pip, etc.) and can even detect application vulnerabilities in languages like Java, Python, Go, and Node.js. The GitLab template simply maps Trivy’s findings to the schema GitLab expects for its security reports.
The exact levers you control are primarily the --severity flag and the --ignore-unfixed option. For instance, trivy image --severity HIGH,CRITICAL --ignore-unfixed ... will skip vulnerabilities that have no fix available, which can be useful for reducing noise when a CVE is known but not yet patchable. You can also specify specific vulnerability IDs to ignore using --ignore-vuln CVE-2023-XXXX,CVE-2022-YYYY.
The most surprising aspect for many is how Trivy can also scan for misconfigurations and secrets. While the gl-container-scanning-report.json primarily focuses on vulnerabilities, Trivy’s capabilities extend much further. You can run trivy config /path/to/your/kubernetes/manifests or trivy fs --scrivere-secrets /app to find security issues beyond just package vulnerabilities. This means a single tool can act as your comprehensive security scanner for images, IaC, and code.
The next logical step after integrating vulnerability scanning is to enforce policies and potentially block deployments based on these findings.