Why scanning in the pipeline matters
A vulnerability scanner that runs once a quarter produces a spreadsheet nobody acts on. A scanner that runs on every build, fails when it should and stays quiet when it should not becomes part of how engineers work. That difference is entirely about integration and policy, not about the tool.
Trivy is a good fit: it is open source, fast, needs no server, and handles OS packages, language dependencies and more. We wire it into your pipeline as part of our broader Docker containerisation service and alongside your CI/CD pipeline.
What good looks like
- Every image is scanned after build and before it is pushed or promoted.
- The build fails on findings that are serious and fixable.
- Developers see results in the pull request, with the package, version and fixed version.
- Deployed images are rescanned on a schedule.
- Exceptions are visible, owned and time-limited.
- An SBOM is generated and kept with the release.
A minimal pipeline step
This GitHub Actions job builds an image, scans it, and fails on fixable high and critical issues. Equivalent steps work in GitLab CI.
name: build
on: [pull_request]
jobs:
image:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
context: .
load: true
tags: myapp:${{ github.sha }}
- name: Scan image
uses: aquasecurity/[email protected]
with:
image-ref: myapp:${{ github.sha }}
format: sarif
output: trivy.sarif
severity: HIGH,CRITICAL
ignore-unfixed: true
exit-code: "1"
- name: Upload results
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy.sarif
Version numbers for actions change, so we pin whatever is current when we implement and let Renovate propose updates. The same scan from a shell is a single line:
trivy image --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 myapp:1.4.2
Designing the policy
Severity and fix availability
Failing on critical and high with a fix available is a good starting point. Those are the findings a developer can act on today, by updating a package or rebuilding on a patched base. Findings without a fix are tracked, not blocking.
Where findings come from
In our experience, the largest share of findings in a typical image comes from the base operating system layer, not your application. That is why base image standardisation and image size reduction often cut scanner output more than any amount of triage.
Exceptions
Each exception needs a CVE identifier, a reason, an owner and a date to review. Keep them in version control. For findings where the vulnerable code is present but not reachable, a VEX statement documents that decision in a standard format.
Beyond vulnerabilities
Trivy can also scan more than packages:
trivy config .checks Dockerfiles, Terraform and Kubernetes manifests for risky settings.trivy fs --scanners secret .looks for committed secrets.trivy image --format cyclonedxproduces an SBOM you can store with each release.
We enable these where they add signal. Not every check belongs in the blocking path on day one.
Scheduled rescans in practice
New advisories land daily, so a scan at build time is a snapshot. The scheduled job lists the images currently deployed, scans each against the latest database and reports only what changed since the last run. A sketch of the workflow trigger:
on:
schedule:
- cron: "0 5 * * *"
workflow_dispatch: {}
The job output should go somewhere a person will see it: a ticket for the owning team, or a message to a security channel, carrying the service, the package and the fixed version. A rescan that nobody reads is no better than no rescan. When a critical issue appears, the answer to “which services are affected?” should be a list, and the answer to “what do we do?” should be to rebuild on a patched base, as described in base image standardisation.
Reading a finding
Developers who have never read scanner output need a short guide. Each finding has a package, an installed version, a fixed version (if any), a severity and a CVE identifier. The useful questions are: is the package actually used by my application or only present in the base image, is a fixed version available, and does the vulnerable code path apply to how we use it? Most of the time the fix is to update the base image or bump a dependency, not to analyse the CVE in depth. We include this guide in the handover so triage does not depend on a security specialist.
Keeping the scanner itself trustworthy
A security tool is part of your supply chain. We pin the scanner action or binary version, verify downloads where the project provides checksums or signatures, and update through reviewed pull requests like any other dependency. We also check that a deliberately vulnerable test image fails the gate, so you learn immediately if a configuration change has silently disabled it.
Decisions and trade-offs
Trivy, Grype, or the registry scanner? All three are credible. Trivy covers the widest scope in one binary. Grype pairs well with Syft for SBOM-centred workflows. Registry scanners give continuous monitoring without CI changes. We use what fits your tooling and avoid running several scanners that disagree and generate duplicate work.
Scan before or after pushing? Scan before the push to keep bad images out of the registry. Rescan after to catch new disclosures.
Database downloads in CI. Trivy fetches a vulnerability database each run. Cache it between runs, or mirror it internally, to avoid rate limits and slow jobs.
When not to gate hard. On a legacy estate with hundreds of existing findings, an immediate blocking gate stops all delivery. We start in report-only mode, fix the biggest sources, then turn on enforcement for new findings.
Common failure modes
- Alert fatigue from thousands of unfixable findings. We filter by fixability and prioritise.
- Bypassed scans because the pipeline allows overriding failures without a record. We require an exception entry instead.
- Stale databases giving false confidence. We check that the database update step succeeds.
- Scanning only the app image and ignoring sidecars, init containers and third-party images you run.
Delivery and handover
We provide pipeline templates for GitHub Actions and GitLab CI, a policy document with severities and exception rules, a scheduled rescan job, and a short runbook for triage. Findings feed the same process as the rest of your container hardening work.
How to tell it is working
Builds fail rarely and for good reasons, mean time to fix a critical finding drops, the exception list is short and current, and a newly published critical CVE produces a list of affected services within a day. To set this up in your pipelines, contact us.