Why use Trivy to extend pod security on Kubernetes
Life as a platform engineer
At my current assignment, I work as part of a platform engineering team. We have deployed a large number of applications to a Kubernetes cluster. Although most of these applications are not internet-facing, we are constantly looking for ways to improve our platform. But next to making things better, we also try to make things safer from external treats. But improving software isn’t just about quality, it is also about security. Protecting our workloads against newly discovered vulnerabilities is equally important.
Trivy against vulnerabilities
Our created software is packaged and published in containers. Also, on other levels we use container images. For that reason, we use Trivy (https://trivy.dev/; the all-in-one security scanner), a popular open-source vulnerability scanner. Initially, we incorporated the scan in our CI/ CD pipeline only. But then we realised that a deployed image, that is safe now, can ‘become’ vulnerable while it is running in production at the time a new CVE (https://www.cve.org/) is found.
Because of these reasons, we use Trivy on multiple levels. Find below our (simplified) implementation of the pipeline.
include:
- project: common-ci-project
file:
- /image/build.yaml
- /version/env.yaml
stages:
- build
- test
- deploy
# Stage .pre
version:env:image:
extends: .version:env:image
# Stage Build
image:build:
extends: .image:build
variables:
IMAGE_NAME: my-image
# Stage test - vulnerabilities
image:trivy:
stage: test
image:
name: our-harbor-proxy.nl/dockerhub/aquasec/trivy:0.67.2
entrypoint: [""]
allow_failure: false #strict
variables:
IMAGE_NAME: my-app
TRIVY_AUTH_URL: ${CI_REGISTRY}
TRIVY_CACHE_DIR: ".trivycache/"
TRIVY_NO_PROGRESS: "true"
TRIVY_PASSWORD: ${CI_REGISTRY_PASSWORD}
TRIVY_USERNAME: ${CI_REGISTRY_USER}
before_script:
- FULL_IMAGE_NAME=${CI_REGISTRY_IMAGE}/${IMAGE_NAME}:${CUSTOM_TAG:-${IMAGE_VERSION:-${CI_COMMIT_TAG:-latest}}}
- trivy --version
# Update vulnerabilities db.
- |
retries=3
while [ $retries -gt 0 ]; do
if trivy image --download-db-only --db-repository "our-harbor-proxy.nl/ghcrhub/aquasecurity/trivy-db:2"; then
break
fi
retries=$((retries - 1))
echo "Retrying DB download... attempts left: $retries"
sleep 5
done
if [ $retries -eq 0 ]; then
echo "Failed to download vulnerability database after 3 attempts" && exit 1
fi
script:
# Builds report and puts it in the default workdir $CI_PROJECT_DIR, so `artifacts:` can take it from there
- >
trivy image
--skip-db-update
--scanners vuln
--exit-code 0
--format template
--template "@/contrib/gitlab.tpl"
--java-db-repository "our-harbor-proxy.nl/ghcrhub/aquasecurity/trivy-java-db:1"
--output "${CI_PROJECT_DIR}/gl-container-scanning-report.json" "${FULL_IMAGE_NAME}"
# Prints full report
- >
trivy image
--skip-db-update
--skip-java-db-update
--scanners vuln
--exit-code 0
"${FULL_IMAGE_NAME}"
# Fail on critical vulnerabilities
- trivy image
--skip-db-update
--skip-java-db-update
--scanners vuln
--ignore-unfixed
--exit-code 1
--severity CRITICAL
"${FULL_IMAGE_NAME}"
cache:
paths:
- .trivycache/
artifacts:
when: always
reports:
container_scanning: gl-container-scanning-report.json
If you prefer a more permissive approach, for example while gradually introducing vulnerability scanning, you can configure the job as follows:
allow_failure: true #permissive
When running a report is created, which can look like this:
The day after
So, is this our final state? Of course not. As you can imagine, we are proud of what we accomplished. But sitting still is not in our mindset. With next generations of tools coming up, we see new opportunities, new improvements and a better and safer result. Even with Trivy in the pipeline, there are more options to improve, like using it in combination with the Trivy Operator (https://aquasecurity.github.io/trivy-operator/latest/). This operator extends the usage of Trivy functionality to the running objects on the cluster. Every relevant object is scanned and delivers one or more reports. An example is the vulnerability report. This report -generated as a CRD – contains the current vulnerabilities found of a deployment, replicaset, etcetera. The report shows, amongst others, the CVE id and Severity.
Find below a partial example of a vulnerability report for a replicaset.
❯ k get vulnerabilityreports.aquasecurity.github.io replicaset-5d87f7f4f4 -o yaml
apiVersion: aquasecurity.github.io/v1alpha1
kind: VulnerabilityReport
metadata:
annotations:
trivy-operator.aquasecurity.github.io/report-ttl: 24h0m0s
creationTimestamp: "2025-12-11T18:11:00Z"
generation: 1
name: replicaset-5d87f7f4f4
namespace: monitoring
artifact:
digest: sha256:7200df84a46ab7f4483f6e8a229afaa91483a0d46ad29a9c10ba6a4874db475d
repository: dockerhub/bitnamilegacy/thanos
tag: 0.37.2-debian-12-r9
scanner:
name: Trivy
vendor: Aqua Security
version: 0.66.0
summary:
criticalCount: 2
highCount: 16
lowCount: 0
mediumCount: 30
noneCount: 0
unknownCount: 0
vulnerabilities:
- fixedVersion: 5.2.2
installedVersion: v5.2.1
lastModifiedDate: "2025-04-10T13:15:52Z"
links: []
packagePURL: pkg:golang/github.com/golang-jwt/jwt/v5@v5.2.1
primaryLink: https://avd.aquasec.com/nvd/cve-2025-30204
publishedDate: "2025-03-21T22:15:26Z"
resource: github.com/golang-jwt/jwt/v5
score: 7.5
severity: HIGH
target: ""
title: 'golang-jwt/jwt: jwt-go allows excessive memory allocation during header
parsing'
vulnerabilityID: CVE-2025-30204
SBOM – Dependency Track
The Trivy operator also generates SBOMs (Software Bill of Material). With a complimentary tool – Dependency-Track (https://dependencytrack.org/), this SBOM can be used to give even better insights. The vulnerability reports of Trivy-operator itself are not used. Dependency-Track imports the SBOM and analyses it from its own vulnerability sources. From that analysis data is exposed in a front-end, which gives you insight from different angles. You can even create your own policies which may result in an overview showing the violations.

The end?
So you think this is the end?
Well… not quite. This is a new beginning.
As you might have guessed, creating a big, beautiful overview has consequences. Our next step is investigating the outcome and fix the critical vulnerabilities ASAP.





