The wrong way to do container security is to scan source code and assume the image will probably look similar. The release artifact is the image, so that is what your policy should evaluate. Everything else is a proxy.
Trivy is popular for a reason: one tool can look at vulnerabilities, misconfigurations, secrets, and SBOM-related detail. The trick is not installing it. The trick is setting thresholds, caching, and workflow placement so developers keep taking the results seriously.
TL;DR
- Scan the built image, not just the repository checkout.
- Cache Trivy data and limit the gate to high-confidence findings if you want CI to stay fast.
- Use severity and fixability rules that match how your team actually remediates issues.
- A scanner that fails every build eventually teaches the team to ignore security.
| Target | When | Command |
|---|---|---|
| Image | After docker build | trivy image my-app:latest |
| Filesystem | Early repo check | trivy fs --scanners vuln,secret,misconfig . |
| Kubernetes | Cluster summary | trivy k8s --report summary cluster |
| SBOM output | Artifact generation | syft my-app:latest -o cyclonedx-json |
Start here: Start with Scan the built image, not just the repo. That is the decision that makes the rest of the workflow coherent.

Scan the built image, not just the repo

The image is where the operating-system packages, copied files, and built dependencies actually come together. That makes it the right unit for a shipping decision. A repository scan is still useful, but it answers a different question from an image scan.
When teams only scan the repo, they miss drift introduced by the base image, the package manager state inside the build, or files copied during the Docker build. Scanning the image closes that gap and gives you a result you can tie directly to the artifact digest.
Use the GitHub Action to keep the workflow readable

The official Trivy action keeps a container-scan job understandable. That matters because security jobs become shelfware when nobody wants to touch the YAML after the first engineer leaves. A readable job is maintainable policy.
The minimum useful gate is usually a built image, a severity threshold, and an explicit exit code. That is enough to make the job enforceable without pretending every low-severity library issue deserves to block release.
Caching and severity discipline are what make the job usable

A slow scan is not just annoying. It changes behaviour. Developers stop waiting for the result, reviewers stop checking it, and the organisation quietly learns that security checks are background noise. Trivy caching matters because it removes a lot of that friction.
Just as important is limiting the failing gate to findings that the team is prepared to act on. Critical and high findings are a reasonable starting point. Fixability, exploitability, and whether the vulnerable component is even in the runtime path all matter when you tune the policy later.
Know when to fail and when to warn

Not every vulnerability report deserves the same reaction. A critical issue in a live runtime dependency with a known fix is different from an unfixed medium issue in a build-only toolchain component. DevSecOps gets stronger when policy acknowledges that difference instead of flattening it.
If you are already using GitHub branch protections, your post on automating branch protection rulesets is a good companion here. The scan matters more when the merge rules around it are explicit and enforced.
Example workflow

These are the two snippets most teams need first: a local image scan and a CI job that gates on serious findings.
Local image scan with a hard failure on high and critical findings:
GitHub Actions job using the official Trivy action:
FAQ
Should I scan the filesystem or the image?
If you build containers, the image is the better release gate because it reflects what you will actually ship. Filesystem scans are still useful earlier in the pipeline for fast feedback.
Why do Trivy scans feel slow sometimes?
Database downloads and repeated cold starts are the usual reason. Keep caching enabled and avoid rebuilding the same scan context unnecessarily.
Do I fail on every finding?
No. Start with a narrow fail policy, usually critical and high findings that are relevant to the shipped artifact. Broader policies can come later once the team trusts the workflow.
Related: GitHub CLI Secrets: Automate Branch Protection Rulesets is the policy-side companion to this article. The scan becomes much more valuable when the merge rules around it are explicit.

