Where it goes in the pipeline
flowchart LR
C["Checkout"] --> FS["trivy fs + config<br/>(fast, on PRs)"]
FS --> B["docker build"]
B --> IM{"trivy image<br/>--exit-code 1"}
IM -- "HIGH/CRITICAL found" --> X["Fail, do not push"]
IM -- "Clean" --> P["Push to registry"] --> D["Deploy"]
Scan the image before pushing so a vulnerable image never reaches the shared registry. By default Trivy exits 0 even with findings; to block, set --exit-code 1.
trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed example-org/myapp:1.0
A common pattern is two runs: one full report for reading (exit 0) and one with only the blocking threshold (exit 1).
GitHub Actions
- name: Build
run: docker build -t example-org/myapp:${{ github.sha }} .
- name: Trivy scan
uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: example-org/myapp:${{ github.sha }}
severity: HIGH,CRITICAL
ignore-unfixed: true
exit-code: '1'
format: sarif
output: trivy.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with: { sarif_file: trivy.sarif }
Uploading SARIF shows results under Security → Code scanning and on the PR. Pin actions to a commit SHA: in March 2025 the tags of the popular tj-actions/changed-files action were moved to malicious code that printed secrets into logs, a textbook supply-chain attack.
Jenkins and GitLab
stage('Trivy') {
steps {
sh 'trivy image --exit-code 1 --severity CRITICAL --no-progress example-org/myapp:${BUILD_NUMBER}'
}
}
GitLab's Container Scanning template uses Trivy under the hood; Azure DevOps runs the trivy command in a script step. --format template --template "@contrib/junit.tpl" produces a JUnit report that Jenkins and GitLab can display.
Time-limited exceptions
# .trivyignore — ID, reason, expiry
# No fix yet and the package is never called. Review before 2026-12-31
CVE-2024-EXAMPLE exp:2026-12-31
Exceptions need a reason, an approver and an expiry date so they do not become a permanent hiding place. The YAML format .trivyignore.yaml can also record paths and statements.
Traps: scanning after push or deploy; forgetting --exit-code so the pipeline is always green; blocking on every severity so developers ignore it; not caching the DB so jobs are slow.