Trivy Container Image Scanning in CI/CD: GitHub Actions Integration, Severity Thresholds, and IaC Scanning

Retool's new app builder is where AI-generated code ships safely
Building apps with AI is easy. Getting them to production safely is another story.
The default Trivy setup in most CI/CD pipelines — trivy image myimage:latest followed by exit code check — scans only the build-time image snapshot against the vulnerability database that was current at scan time. The image ships to production, runs for four months, and accumulates newly published CVEs that will never trigger an alert because the build-time scan already passed. The second problem is threshold misconfiguration: teams either set --severity CRITICAL and get zero findings (because CRITICAL CVEs in modern base images are genuinely rare), or they leave no threshold and get 300 findings including MEDIUM/LOW issues from libraries that are not reachable by the application.
Trivy's effective configuration requires three decisions: severity threshold calibrated to what you can actually remediate, a .trivyignore process that suppresses accepted-risk CVEs with documentation, and in-cluster scanning via Trivy Operator to catch post-deployment CVE disclosures. Getting all three right takes a few hours and produces a scanner configuration that provides genuine signal rather than noise.
Build-time scanning: GitHub Actions integration and SARIF output
GitHub Actions integration with the official aquasecurity/trivy-action puts CVE findings directly in the Security tab as code scanning alerts, making vulnerabilities visible to developers in the same interface where they see code review feedback. The SARIF output format is the key enabler: it maps CVE findings to specific lines in the Dockerfile and dependency files, which gives developers the exact location to fix rather than just the CVE ID. Pairing SARIF upload with exit-code: 1 on CRITICAL and HIGH findings creates a blocking gate without hiding findings from developers who need to understand why their build failed.
Configure the Trivy action to scan the built image by digest to prevent TOCTOU between build and scan
Build the container image, capture its digest with docker inspect --format='{{index .RepoDigests 0}}' myimage:latest, and pass the digest to Trivy rather than the tag. Image tags are mutable — an attacker or misconfigured registry could replace the image between build and scan, causing the scan to evaluate a different image than what was built. Using the digest (sha256:abc123...) ensures the scan evaluates exactly the image artifact produced by the build step. In GitHub Actions, use the docker/build-push-action with outputs: type=image,name=myimage,push=true,push-by-digest=true to capture the digest as an output variable and pass it to the trivy-action in the subsequent step.
Run Trivy IaC scanning against Dockerfiles and Kubernetes manifests in the same pipeline
Add a trivy config scan step to the same CI/CD workflow that runs image scanning, targeting the Dockerfile and Kubernetes manifests in the repository: trivy config --severity HIGH,CRITICAL --exit-code 1 . This catches Dockerfile misconfigurations (COPY with root ownership, USER root, ADD from URL rather than COPY) and Kubernetes manifest issues (no resource limits, missing security context) in the same pipeline run as the image CVE scan. Running both in one workflow provides a complete picture of container security issues at the point in the development cycle when they are cheapest to fix.
In-cluster scanning: Trivy Operator for continuous CVE monitoring
Build-time scanning solves the known-CVE-at-build problem but leaves a gap: images that passed their original scan accumulate new CVEs during the weeks or months they run in production, and no alert fires until the next deployment triggers a new scan. The Trivy Operator closes this gap by watching running pods in the cluster, scanning their images against the daily-updated vulnerability database, and generating VulnerabilityReport Kubernetes custom resources that expose CVE status as queryable cluster state. Security teams can query kubectl get vulnerabilityreports -A at any time to see the current CVE posture of every running workload.
Configure the Trivy Operator to generate Prometheus metrics for Grafana alerting on new critical CVEs
The Trivy Operator exports Prometheus metrics at /metrics including trivy_image_vulnerabilities with labels for namespace, pod name, container name, and severity. Configure a Prometheus scrape job targeting the operator service and build a Grafana alert that fires when trivy_image_vulnerabilities{severity='CRITICAL'} increases for any workload. This provides an active alert when a new critical CVE is published against a running image rather than waiting for the next deployment to trigger a build-time scan. Set the alert routing to the team that owns the affected workload using namespace labels so alerts reach the right team rather than a generic security mailbox.
Establish a recurring VulnerabilityReport review to track unfixed CVEs in production
Export Trivy Operator VulnerabilityReport data on a regular cadence with kubectl get vulnerabilityreports -A -o json | jq '.items[] | {namespace: .metadata.namespace, name: .metadata.name, critical: .report.summary.criticalCount, high: .report.summary.highCount}' and track the CVE counts over time in a spreadsheet or security dashboard. For each namespace with nonzero critical counts, identify the workload owner and open a remediation ticket with the SLA deadline. Trend analysis over four to six weeks shows whether the organization's container image update cadence is keeping pace with new CVE publications or whether the unfixed CVE count is growing. A growing count indicates that teams are not updating base images frequently enough, which requires a process change rather than just individual remediation tickets.
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
The bottom line
Trivy is the right tool for container image security scanning, but the default configuration leaves two critical gaps: build-only scanning that misses post-deployment CVE publications, and a severity threshold that generates either zero findings or hundreds of informational findings. Fix build-time scanning by integrating the aquasecurity/trivy-action with SARIF output to GitHub Security, using exit-code: 1 on CRITICAL and HIGH with available fixes, and documenting accepted-risk CVEs in a version-controlled .trivyignore file. Fix the post-deployment gap by deploying Trivy Operator into the cluster and configuring Prometheus alerting on the VulnerabilityReport metrics. Add trivy config IaC scanning to the same pipeline for Dockerfile and Kubernetes manifest misconfiguration detection. Together these three configurations provide continuous CVE visibility across the full container lifecycle rather than a one-time snapshot at build time.
Frequently asked questions
How do I integrate Trivy into a GitHub Actions CI/CD pipeline?
Integrate Trivy into GitHub Actions using the official aquasecurity/trivy-action. Add a scan job step after the Docker build step: uses: aquasecurity/trivy-action@master with with parameters image-ref: ${{ env.IMAGE_NAME }}:${{ github.sha }}, format: sarif, output: trivy-results.sarif, severity: CRITICAL,HIGH, exit-code: 1. The exit-code: 1 parameter causes the action to fail the workflow run if any CRITICAL or HIGH CVEs are found with available fixes. Add a subsequent step to upload the SARIF results to GitHub Security: uses: github/codeql-action/upload-sarif@v3 with sarif_file: trivy-results.sarif, which populates the Security tab with findings including CVE IDs, affected packages, and fix versions. This gives developers visibility into vulnerabilities without requiring them to run Trivy locally during development.
How do I configure a .trivyignore file to suppress accepted-risk CVEs?
Create a .trivyignore file in the repository root (or specify its path with --ignorefile) to suppress specific CVEs that have been reviewed and accepted as low-risk for your environment. Each line in the file is a CVE ID to ignore: CVE-2023-12345, one CVE per line. Trivy automatically reads .trivyignore when running trivy image or trivy fs and excludes listed CVEs from results and from the exit code evaluation. For team accountability, add a comment syntax using # to document why each CVE is suppressed: # Accepted 2026-03-15 - no network exposure, mitigated by WAF rule, reviewed by @security-team. Store the .trivyignore file in version control so that suppressions are reviewed in pull requests and have a documented history. Review the .trivyignore entries quarterly and remove CVEs that now have available fixes or whose mitigating controls have changed.
How do I use Trivy to scan Terraform and Kubernetes IaC files?
Scan Terraform files and Kubernetes manifests with Trivy using the config scan mode: trivy config ./terraform-directory scans all .tf files for misconfigurations (open security groups, missing encryption, public S3 buckets, unencrypted RDS) using the same policy set as Checkov and tfsec. For Kubernetes manifests: trivy config ./k8s-manifests scans YAML files for security policy violations including privileged containers, missing network policies, and insecure pod security settings. Integrate IaC scanning into the same GitHub Actions workflow as image scanning with separate steps: one step for trivy config ./terraform with --exit-code 1 --severity HIGH,CRITICAL, and another for trivy config ./k8s-manifests. The IaC scan runs against the repository files on every pull request, so infrastructure misconfigurations are caught at code review time rather than after Terraform apply or kubectl apply.
How do I deploy the Trivy Operator for continuous in-cluster scanning?
Deploy the Trivy Operator into your Kubernetes cluster using Helm: helm repo add aqua https://aquasecurity.github.io/helm-charts/ then helm install trivy-operator aqua/trivy-operator --namespace trivy-system --create-namespace. The operator watches for running pods and automatically scans their container images, generating VulnerabilityReport custom resources in the same namespace as the workload. Query current vulnerability status across all workloads with kubectl get vulnerabilityreports -A which lists every scanned image with its critical and high CVE counts. The operator rescans images when the Trivy vulnerability database is updated (approximately daily), so newly published CVEs against production images generate new VulnerabilityReport objects without requiring a re-deployment or pipeline run. Configure alerting by watching for VulnerabilityReport objects with nonzero criticalCount using a Prometheus metric exported by the operator or by querying the Kubernetes API from a scheduled job.
How do I handle Trivy false positives and CVEs without available fixes?
Handle Trivy false positives and unfixable CVEs with a documented suppression process rather than ignoring them ad hoc or lowering the severity threshold globally. For CVEs with no available fix (status UNFIXED in Trivy output), use --ignore-unfixed flag to exclude them from exit code evaluation while still displaying them in the report: trivy image --ignore-unfixed --severity CRITICAL,HIGH myimage:latest fails only when CVEs with available fixes are found. For CVEs that are false positives or accepted risks specific to your environment, add them to .trivyignore with a justification comment. The critical discipline is separating unfixed CVEs (which should not block deployment since you cannot fix them without the upstream vendor releasing a patch) from fixed CVEs with available upgrades (which should block deployment because the remediation action is clear). Review unfixed CVEs monthly and update base images when fixes become available.
How do I scan images in a private container registry with Trivy?
Scan images from private registries by providing authentication credentials to Trivy before scanning. For AWS ECR, authenticate the Docker daemon with aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com and then run trivy image ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/my-image:tag which uses the Docker credentials automatically. For Google Artifact Registry, authenticate with gcloud auth configure-docker then run trivy image us-central1-docker.pkg.dev/PROJECT/REPO/IMAGE:TAG. In GitHub Actions, authenticate to the registry in a step before the Trivy scan step using the registry-specific login action (aws-actions/amazon-ecr-login or docker/login-action). For air-gapped environments where the Trivy vulnerability database cannot be downloaded at scan time, pre-download the database with trivy image --download-db-only and cache it as a CI/CD artifact or bake it into a custom scanner image that is updated daily from a network-connected environment.
How do I set severity thresholds to avoid alert fatigue without missing real risks?
Set Trivy severity thresholds based on exploitability and fixability rather than using a blanket block-on-all-critical approach that generates too many findings to address. Start by blocking only CRITICAL CVEs with available fixes in the initial pipeline integration to avoid breaking builds before teams have capacity to remediate. After two weeks of remediation, add HIGH CVEs with available fixes to the blocking set using --severity CRITICAL,HIGH combined with --ignore-unfixed. Use the --format table output in development builds to show developers the full CVE list including MEDIUM and LOW findings as informational output without blocking, and use --format sarif for the Security tab upload so the full vulnerability landscape is visible to the security team. Establish a remediation SLA policy: CRITICAL with fix available within 7 days, HIGH with fix available within 30 days, MEDIUM within 90 days. Track compliance with the SLA using the Trivy Operator VulnerabilityReport data in a Grafana dashboard built from the Prometheus metrics the operator exports.
Sources & references
Free resources
Critical CVE Reference Card 2025–2026
25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.
Ransomware Incident Response Playbook
Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.
Get threat intel before your inbox does.
50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.
Unsubscribe anytime. We never sell your data.

Founder & Cybersecurity Evangelist, Decryption Digest
Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.
Win a $2,495 Black Hat pass.
Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.
