In the modern DevOps ecosystem, Docker has become an essential standard for application containerization. However, with the growing popularity of containers come significant security concerns. Docker images can contain critical vulnerabilities from their dependencies, exposing your applications to serious risks.
Trivy is an open-source vulnerability scanner developed by Aqua Security, simple, fast, and comprehensive. It detects CVEs (Common Vulnerabilities and Exposures) in your Docker images, file systems, Git repositories, and more.
This article will guide you through using Trivy to secure your Docker images, with practical examples and integration into your CI/CD pipeline.
Docker containers share the host system's kernel, meaning a vulnerability in one container can potentially compromise the entire system. Risks include:
Trivy stands out from other scanners by:
brew install aquasecurity/trivy/trivysudo apt-get install wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:latest image [IMAGE_NAME]trivy --versionThe simplest command to scan an image:
trivy image node:18-alpineResult: Trivy will:

To focus only on critical and high vulnerabilities:
trivy image --severity CRITICAL,HIGH node:18-alpinetrivy image --ignore-unfixed node:18-alpineThis displays only vulnerabilities for which a patch exists.
Useful for CI/CD integration and automated analysis:
trivy image --format json --output trivy-report.json node:18-alpineStandard format for GitHub Security, GitLab Security Dashboard:
trivy image --format sarif --output trivy-results.sarif node:18-alpineDefault format, ideal for human reading:
trivy image --format table node:18-alpineCreate your own report format:
trivy image --format template --template "@contrib/html.tpl" -o report.html node:18-alpineLet's take a problematic Dockerfile based on an old Ubuntu version:
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y \
curl \
git \
python3 \
python3-pip
COPY app.py /app/
WORKDIR /app
CMD ["python3", "app.py"]Build and scan:
docker build -t vulnerable-app:1.0 .
trivy image --severity HIGH,CRITICAL vulnerable-app:1.0Result: Probably 50+ CRITICAL/HIGH vulnerabilities.
Solution: Improved Dockerfile:
# Use a more recent and minimal base image
FROM ubuntu:22.04
# Install packages and update to fix vulnerabilities
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
git \
python3 \
python3-pip \
&& apt-get upgrade -y \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Create a non-root user
RUN useradd -m -u 1000 appuser
COPY --chown=appuser:appuser app.py /app/
WORKDIR /app
USER appuser
CMD ["python3", "app.py"]Re-scan:
docker build -t secure-app:1.0 .
trivy image --severity HIGH,CRITICAL secure-app:1.0Improvement: Drastic reduction in vulnerabilities (typically 90%+ improvement).
You can scan your Dockerfile before even building the image:
trivy config DockerfileThis detects:
trivy repo https://github.com/your-org/your-projectTrivy will scan:
Create .github/workflows/trivy-scan.yml:
name: Trivy Security Scan
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 2 * * *' # Daily scan at 2 AM
jobs:
trivy-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t ${{ github.repository }}:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
Add to .gitlab-ci.yml:
stages:
- build
- security-scan
build:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker save $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA -o image.tar
artifacts:
paths:
- image.tar
trivy-scan:
stage: security-scan
image: aquasec/trivy:latest
script:
- docker load -i image.tar
- trivy image --exit-code 1 --severity CRITICAL,HIGH $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- trivy image --format json --output trivy-report.json $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
artifacts:
reports:
container_scanning
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'docker build -t myapp:${BUILD_NUMBER} .'
}
}
stage('Trivy Scan') {
steps {
sh '''
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:latest image \
--severity CRITICAL,HIGH \
--exit-code 1 \
--format json \
--output trivy-report.json \
myapp:${BUILD_NUMBER}
'''
}
}
}
post {
always {
archiveArtifacts artifacts: 'trivy-report.json', allowEmptyArchive: true
}
}
Create a .trivyignore file at the root of your project:
# Ignore CVE-2023-1234 as not applicable to our use case
CVE-2023-1234
# Temporarily ignore while waiting for a patch
CVE-2023-5678 # TODO: Upgrade when version 2.0 is availableThen run Trivy:
trivy image --ignorefile .trivyignore myapp:latestTo speed up scans in CI/CD, cache the database:
GitHub Actions:
- name: Cache Trivy DB
uses: actions/cache@v4
with:
path: ~/.cache/trivy
key: ${{ runner.os }}-trivy-${{ github.run_id }}
restore-keys: |
${{ runner.os }}-trivy-Manual DB update:
trivy image --download-db-only# Scan only OS packages
trivy image --scanners vuln myapp:latest
# Scan OS + application dependencies
trivy image --scanners vuln,config myapp:latest
# Scan everything (vulnerabilities + secrets + misconfiguration)
trivy image --scanners vuln,secret,config myapp:latestUse --exit-code with conditions:
# Fail only if CRITICAL
trivy image --severity CRITICAL --exit-code 1 myapp:latest
# Fail if CRITICAL or HIGH
trivy image --severity CRITICAL,HIGH --exit-code 1 myapp:latestlatest.trivyignore with explanatory commentslatest: Always use fixed versionsSymptom: Trivy reports 200+ vulnerabilities on an official image.
Solution:
--severity CRITICAL,HIGH--ignore-unfixed# Before
trivy image ubuntu:20.04 # 150 vulnerabilities
# After
trivy image ubuntu:22.04 # 20 vulnerabilities
trivy image alpine:3.19 # 2 vulnerabilitiesSymptom: The scan takes several minutes.
Solution:
# Use local cache
trivy image --cache-dir ~/.cache/trivy myapp:latest
# Scan only OS packages (faster)
trivy image --scanners vuln myapp:latest
# Disable secret scanning (if not needed)
trivy image --scanners vuln,config myapp:latestSymptom: Error: failed to download vulnerability DB
Solution:
# Manually download the DB
trivy image --download-db-only
# Use an alternative mirror
trivy image --db-repository ghcr.io/aquasecurity/trivy-db myapp:latest
# Offline mode (if DB already downloaded)
trivy image --skip-db-update myapp:latestTrivy is an indispensable tool in every security-conscious DevOps professional's toolkit. Its ease of use, speed, and accuracy make it the ideal choice for scanning vulnerabilities in your Docker images.
trivy image.trivyignore to document exceptionsThank you for following me on this journey! 🚀
This article was written with ❤️ for the DevOps community.