Container image scanning is the automated analysis of a container image’s layers, operating system packages, application dependencies and build configuration against vulnerability databases. It finds known CVEs, embedded secrets and risky settings before the image runs in production. Every image inherits code from a base image and pulls in open-source libraries nobody on the team reviewed line by line, so the image is the most practical checkpoint in the software supply chain. Image-only scanning is still incomplete. It reports what is installed, but it cannot tell you what is loaded, exposed or under attack. This guide, current as of October 2026, explains how scanners work internally, where they fall short, how to add them to CI/CD and Kubernetes, and which tools to consider.
Key takeaways
- ✓Container image scanning inventories every package in an image’s layers and matches it against advisory sources such as NVD, distro security trackers, the GitHub Advisory Database and OSV.
- ✓For OS packages, distro advisories are more accurate than raw NVD matching because they account for backported fixes that leave the upstream version number unchanged.
- ✓Storing an SBOM for every image lets teams check for newly disclosed CVEs without pulling and unpacking the images again.
- ✓Build-time gates, registry rescans, signature checks at admission and runtime monitoring each catch problems the other stages miss.
- ✓Runtime evidence of which vulnerable packages are actually loaded and internet-exposed is the most reliable way to rank a large CVE backlog.
What container image scanning covers, and where it stops
Container image scanning covers the static contents of an image: OS packages, language libraries, binaries and image metadata. Secret detection, IaC checks, license compliance, malware detection, registry rescans and runtime monitoring are adjacent capabilities. Many vendors bundle all of these under the broader label “container security scanning,” which is why the two terms often get used as if they meant the same thing.
The distinction matters when you buy or build a pipeline. A container vulnerability scanner that only matches packages to CVEs will not flag an AWS key baked into a layer, a Kubernetes manifest that grants privileged: true, or a cryptominer started by a compromised process.
| Capability | What it checks | Part of core image scanning? |
|---|---|---|
| Image vulnerability scanning | OS and language packages matched to CVEs and advisories | Yes, this is the core function |
| Image configuration checks | Running as root, exposed ports, missing HEALTHCHECK, latest base tags |
Usually included |
| Secret detection | API keys, private keys and tokens in layers or environment variables | Often bundled, but a separate engine |
| IaC and manifest scanning | Dockerfiles, Helm charts, Kubernetes YAML, Terraform | Adjacent: scans the definitions, not the image |
| License compliance | GPL, AGPL and other licenses in dependencies | Adjacent: uses the same package inventory |
| Malware detection | Known-bad binaries and suspicious files | Adjacent: needs signatures or behavior analysis |
| Registry scanning | Stored images rescanned as advisories change | Same engine, different trigger |
| Runtime monitoring | Processes, syscalls, network flows and loaded libraries in live containers | No, this is workload protection |
Static, registry and runtime scanning compared
The same image gets evaluated at three points in its life. Each point sees something different. Registries such as Harbor can scan Docker images for vulnerabilities on push, which keeps findings current for images that are no longer being rebuilt. Our guide to registry vulnerability scanning covers that layer in more depth.
| Static (build-time) scanning | Registry scanning | Runtime scanning | |
|---|---|---|---|
| When it runs | In CI, after docker build |
On push and on a schedule | Continuously, on running workloads |
| What it sees | Installed packages and config in one image | Every stored image, including old tags | Loaded packages, processes, network exposure |
| Main strength | Blocks bad images before they ship | Catches CVEs disclosed after build | Shows which findings are actually exploitable |
| Blind spot | No idea whether code ever executes | No deployment or exposure context | Only sees what is already deployed |
How container image scanning finds vulnerabilities
Container image scanning finds vulnerabilities by unpacking an image into its filesystem, building an inventory of every OS and language package, and comparing each package version against vulnerability advisories. A scanner runs the steps below in order. Most false positives and false negatives trace back to one of them.
- Resolve the image. The scanner pulls the image by tag or digest and reads the OCI manifest and config JSON, which list the layers, environment variables, user and exposed ports.
- Extract and flatten layers. Each layer is a tar archive. The scanner applies them in order and honors whiteout files (
.wh.*) so a package deleted in a later layer is not reported. Good scanners also record which layer introduced each package, so fixes target the right Dockerfile line. - Detect the OS. Files such as
/etc/os-releaseor/etc/alpine-releaseidentify the distribution and release, for example Debian 12 or Alpine 3.20. Distro version decides which advisory feed applies. - Enumerate OS packages. The scanner parses the package database:
/var/lib/dpkg/statusfor Debian and Ubuntu, the RPM database for RHEL, Fedora and Amazon Linux, and/lib/apk/db/installedfor Alpine. - Parse language manifests and binaries. It reads
package-lock.json,requirements.txtand PythonMETADATAfiles,Gemfile.lock,Cargo.lock,pom.propertiesinside JARs (including nested fat JARs), and Go build info embedded in compiled binaries. - Normalize identifiers. Each component is expressed as a package URL (for example
pkg:deb/debian/[email protected]~deb12u2) or a CPE string. Package URLs map cleanly to ecosystem advisories. CPEs are what NVD uses, and they are fuzzier. - Correlate with advisories. Versions are compared to affected ranges using each ecosystem’s rules: dpkg epochs, RPM epoch-version-release, or semver. Treating
1:2.3as a plain string is a classic source of wrong results. - Filter and report. The scanner applies distro “not affected” statuses, VEX statements and ignore lists. It then outputs each finding with severity, CVSS score, fixed version and originating layer, in formats such as JSON, SARIF, CycloneDX or SPDX.
Vulnerability databases and advisory sources
| Source | Coverage | Strength | Watch out for |
|---|---|---|---|
| NVD | All published CVEs, with CVSS and CPE data | Broadest reference, standard severity scoring | CPE matching is noisy and ignores distro backports |
| Distro advisories (Debian Security Tracker, Ubuntu OVAL, Red Hat CSAF/OVAL, Alpine secdb) | Packages as built by each distribution | Accurate fixed versions and “not affected” or “won’t fix” status | Only valid for that distro and release |
| GitHub Advisory Database (GHSA) | npm, PyPI, Maven, Go, RubyGems, NuGet, Rust and more | Reviewed advisories with exact affected version ranges | Ecosystem packages only, no OS packages |
| OSV (osv.dev) | Aggregates GHSA, PyPI, Go, Rust, distro feeds and others | One machine-readable schema with precise ranges | Quality depends on the upstream feed |
For prioritization, many teams add CISA’s Known Exploited Vulnerabilities (KEV) catalog and FIRST’s EPSS exploit-probability scores on top of these sources.
SBOMs and rescanning when new CVEs appear
A software bill of materials (SBOM) is the package inventory from steps 4 to 6, saved as a file. The two dominant formats are SPDX, a Linux Foundation standard (ISO/IEC 5962) with strong license detail, and CycloneDX, an OWASP standard built for security use cases that also supports VEX data. Tools such as Syft or trivy image --format cyclonedx generate them at build time.
SBOMs change how you respond to a new CVE. When Log4Shell (CVE-2021-44228) hit, teams without inventories had to pull and unpack every image to look for log4j-core, often buried inside fat JARs. With stored SBOMs, you rerun matching against updated feeds (for example grype sbom:./api-sbom.cdx.json) and get a list of affected images in minutes.
Limitations of image scanning
- Unfixed CVEs: Many findings have no patched version yet, or the distro marks them “won’t fix.” Blocking on these stalls every build.
- Distro backports: Red Hat and Debian patch old versions without bumping the upstream number. NVD-only matching then flags fixed packages as vulnerable.
- Noisy matches: CPE collisions attach CVEs to the wrong product, for example a Python package that shares a name with an unrelated C library.
- Secret and malware gaps: CVE matching cannot see credentials or malicious binaries. Those need dedicated detectors.
- Private dependencies: Internal packages have no public advisories, so they always look clean.
- Distroless, scratch and copied binaries: Images without a package database, or with tools fetched via
curlin aRUNstep, leave the scanner nothing to enumerate unless it reads binary metadata. - Point-in-time results: A scan only reflects CVEs known when it ran. An unchanged image can fail tomorrow.
Handle false positives with expiring, documented exceptions rather than permanent ignore lists. A VEX statement such as “not_affected: vulnerable_code_not_in_execute_path,” with an owner and a 90-day review date, gives auditors a written reason for each exception.
Container image scanning in CI/CD, GitHub and Kubernetes
Container image scanning in CI/CD runs a scanner right after the image is built, fails the job when findings exceed a threshold, and publishes results to the platform’s security view. Kubernetes admission policies then refuse anything that skipped those checks. The patterns are similar across platforms, and only the syntax changes.
GitHub Actions
Build the image, scan it by commit SHA, and upload SARIF results so findings appear in GitHub code scanning.
- name: Build
run: docker build -t ghcr.io/acme/api:${{ github.sha }} ., name: Scan image
uses: aquasecurity/trivy-action@master # pin a release tag or SHA
with:
image-ref: ghcr.io/acme/api:${{ github.sha }}
severity: CRITICAL,HIGH
ignore-unfixed: true
exit-code: '1'
format: sarif
output: trivy.sarif, name: Upload results
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy.sarifOther GitHub options work the same way. The NeuVector image scan action takes image-repository and image-tag inputs, plus fail thresholds such as min-high-cves-to-fail: "1". It can also scan remote registry images using registry credentials stored as secrets. The open-source vimp tool wraps Grype, Trivy or Snyk (vimp scan --image <image>) and supports SARIF output for GitHub code scanning.
GitLab CI
GitLab ships a built-in Jobs/Container-Scanning.gitlab-ci.yml template. A custom job with Grype looks like this:
container_scan:
stage: test
image: alpine:3.20
script:
- apk add --no-cache curl
- curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
- grype registry:$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --fail-on high -o tableJenkins
Red Hat’s OpenShift image signing and scanning example uses the same flow in Jenkins, scanning after build and before deployment. It also uses a controller that scans images automatically when they are pushed to the registry.
stage('Scan image') {
steps {
sh 'trivy image --exit-code 1 --severity CRITICAL,HIGH --ignore-unfixed acme/api:${GIT_COMMIT}'
}
}Signed images and attestations
A scan result is only useful if the image you deploy is the image you scanned. Sigstore’s Cosign signs the image digest and can attach the scan report as a signed attestation.
cosign sign --key cosign.key ghcr.io/acme/api@sha256:4f1c...
trivy image --format cosign-vuln -o vuln.json ghcr.io/acme/api@sha256:4f1c...
cosign attest --key cosign.key --type vuln --predicate vuln.json ghcr.io/acme/api@sha256:4f1c...Signing proves who built the image. The attestation proves it was scanned, and when. SLSA provenance attestations add which source commit and builder produced the image. Keyless signing with Fulcio certificates and the Rekor transparency log removes the need to manage long-lived keys.
Kubernetes admission policies
Kyverno can verify signatures at admission and rewrite tags to digests, so a re-pushed tag cannot swap the image afterward.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-signed-images
spec:
validationFailureAction: Enforce
rules:
- name: verify-cosign-signature
match:
any:
- resources:
kinds: [Pod]
verifyImages:
- imageReferences: ["ghcr.io/acme/*"]
mutateDigest: true
attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----Kyverno verifyImages rules can also check the vuln attestation, for example rejecting images whose scan is older than 7 days. With OPA Gatekeeper, teams typically pair constraint templates such as allowed repositories and disallowed :latest tags with an external data provider such as Ratify for signature verification.
Best practices: thresholds, SLAs and remediation
Gate builds on fixable, high-impact findings, set remediation deadlines by severity and exposure, and fix problems by rebuilding images rather than patching running containers. That is the best practice for container image scanning. Blocking on every CVE trains developers to bypass the scanner. Blocking on nothing turns scanning into a reporting exercise.
Policy thresholds and SLAs by severity
The table below is an example policy that many teams adapt. Tune the windows to your risk appetite and compliance obligations.
| Severity | CI gate | Example remediation SLA | Escalate when |
|---|---|---|---|
| Critical | Block if a fix exists | 7 days (72 hours if internet-facing) | Listed in CISA KEV or loaded at runtime |
| High | Block if a fix exists | 30 days | High EPSS score or exposed service |
| Medium | Warn | 90 days | Chained with another exposure |
| Low | Report only | Next scheduled base-image rebuild | Rarely |
Runtime context should reorder that queue. A simple prioritization rule looks like this:
priority = severity_rank(cve) # critical=4 ... low=1
if cve.in_kev or cve.epss > 0.1: priority += 2
if package.loaded_at_runtime: priority += 2
if workload.internet_exposed: priority += 1
if not package.loaded_at_runtime and not cve.in_kev:
priority = min(priority, 2) # defer dormant codeFor example, a high-severity OpenSSL CVE in an internet-facing API pod that loads libssl outranks a critical CVE in a perl package that never executes in a batch job. Ranking by exposure first is how mature container vulnerability management works.
Remediation workflows
- Rebuild from a patched base image. Most OS findings come from the base image. Bump
FROM debian:12to the latest point release and rebuild, rather than runningapt-get upgradeon top of a stale base. - Pin by digest, not tag.
FROM python:3.12-slim@sha256:...makes builds reproducible. Let Renovate or Dependabot open pull requests when the digest changes. - Shrink the attack surface. Slim, distroless or Chainguard-style minimal bases drop shells and package managers. Use multi-stage builds so compilers never reach the final image.
- Update application dependencies. Bump the direct dependency that pulls in the vulnerable transitive package, then regenerate lockfiles.
- Handle exceptions formally. Record unfixed or non-exploitable findings as VEX or ignore entries, each with an owner, a justification and an expiry date.
- ✓Scan the same image at build, in the registry on a schedule, and at admission.
- ✓Generate and store an SBOM for every released image digest.
- ✓Use
, ignore-unfixedor its equivalent at the CI gate, but track unfixed CVEs separately. - ✓Run containers as non-root and drop the scanner’s config findings into the same SLA queue.
- ✓Rebuild active images at least weekly, even without code changes, to pick up base-image fixes.
Top container image scanning tools
The leading container image scanning tools fall into two groups. Open-source scanners (Trivy, Grype, Clair) are strong at build-time checks, and commercial platforms (Upwind, Snyk Container, Prisma Cloud, Aqua Security and Wiz) add registry coverage, runtime context and prioritization. Many teams run one of each type.
| Tool | Type | Image scanning strengths | Best fit |
|---|---|---|---|
| Upwind | Commercial CNAPP | CI/CD scanning plus runtime evidence of loaded, reachable, exposed packages | Teams with large CVE backlogs across Kubernetes and multi-cloud |
| Trivy | Open source | Images, filesystems, IaC, secrets, SBOM output | Fast CI gates |
| Grype | Open source | Image and SBOM scanning, paired with Syft | SBOM-driven rescanning |
| Clair | Open source | API-based layer indexing for registries | Self-hosted registry scanning |
| Snyk Container | Commercial | Developer-focused findings and base image upgrade advice | Developer-led remediation |
| Prisma Cloud (Cortex Cloud) | Commercial CNAPP | CI/CD scanning, SCA with license checks, runtime protection | Large Palo Alto Networks environments |
| Aqua Security | Commercial CNAPP | Image assurance policies, malware and secrets checks, signed SBOMs | Kubernetes-heavy enterprises |
| Wiz | Commercial CNAPP | Agentless scanning, CI/CD image checks, SPDX and CycloneDX SBOMs | Agentless-first cloud teams |
Upwind
Upwind is a CNAPP built around runtime data. It combines agentless cloud discovery with lightweight eBPF sensors on VMs, containers and serverless workloads. For image scanning, it plugs into CI/CD pipelines and IaC scanning, provides SBOM visibility, and ranks CVEs by reachability, whether the affected package is actually loaded, internet exposure and exploitability. It can also trace a runtime finding back to the code or configuration that introduced it. One Gartner Peer Insights reviewer wrote that “Upwind found some significant vulnerabilities that were being missed by other products we use for security scanning.” Upwind is rated 4.8/5 from 88 reviews on Gartner Peer Insights as of October 2026.
Trivy
Trivy is an open-source scanner maintained by Aqua Security. It scans container images, filesystems, Git repositories, IaC files and Kubernetes clusters for vulnerabilities, misconfigurations and secrets, and outputs SARIF, CycloneDX and SPDX. Its single binary and offline database mode make it the default CI gate for many teams. It has no runtime context of its own.
Grype
Grype is Anchore’s open-source vulnerability scanner. It pairs with Syft, which generates SBOMs. Grype can scan an image directly or scan a stored SBOM, so it works well for rescanning a fleet when a new CVE lands. Its , fail-on flag gives simple severity gating.
Clair
Clair is an open-source static analyzer that indexes image layers and matches them against advisory feeds through an API. It powers scanning in Red Hat Quay. It fits registry-side scanning better than developer workstations or quick CI jobs.
Snyk Container
Snyk Container is a commercial scanner aimed at developers. It integrates with registries, CI and source repositories, and is known for recommending alternative base images with fewer vulnerabilities. It suits teams that want developers to own fixes directly.
Prisma Cloud (Palo Alto Networks Cortex Cloud)
Cortex Cloud scans IaC templates and Dockerfiles, runs SCA with license compliance checks, integrates with CI/CD pipelines, and traces runtime findings back to code. It prioritizes CVEs by reachability, loaded packages, internet exposure and exploitability. Gartner Peer Insights reviewers praise its Kubernetes and CI/CD coverage. Several also report that custom workflows, API integrations and alert tuning take significant setup work, and some describe the cost as steep.
Aqua Security
Aqua covers image scanning, secrets, malware detection, virtual patching, image assurance policies and digitally signed SBOMs, with eBPF-based runtime protection. Reviewers describe it as mature for Kubernetes environments. Some note that it suits large enterprises with experienced staff better than small teams, because onboarding and tuning demand expertise.
Wiz
Wiz uses agentless snapshot scanning and integrates with CI/CD pipelines to scan images, repositories and configs for vulnerabilities and secrets. It generates SPDX and CycloneDX SBOMs and validates which packages are loaded at runtime. One reviewer noted that its detection and response capabilities still need development. Another said it suits hands-on practitioners better than GRC teams.
How Upwind adds runtime context to image scanning
Upwind runs alongside your build-time scanner and tells you which of its findings matter in production. Keep Trivy or Grype gating pull requests. Upwind’s eBPF sensor then watches the running containers built from those images and flags the CVEs whose packages actually load, sit on a reachable path and face the internet. Each finding links back to the Dockerfile or code change that introduced it, so the owning team gets one fix instead of hundreds of tickets. Some Gartner Peer Insights reviewers say Upwind’s SBOM features still need more polish.
- ✓CI/CD and IaC scanning feed the same risk view as runtime findings.
- ✓CVEs are ranked by loaded packages, reachability, exposure and exploitability.
- ✓Runtime findings trace back to the line of code or configuration.
- ✓Threat Stories correlate runtime, identity and cloud signals when an image is exploited.
- ✓Microsoft Sentinel playbooks can isolate containers or quarantine nodes.
How to choose a container image scanning tool
Test each container image scanning tool on your own images against the failure modes covered above, which are backports, distroless bases, nested JARs and alert volume. Vendor feature lists matter less. Run a two-week trial on 20 to 30 representative images and compare results side by side.
- Accuracy: Does it use distro advisories and VEX, or flag backported packages as vulnerable?
- Coverage: Does it find packages in distroless images, Go binaries and fat JARs?
- Pipeline fit: Does it offer native GitHub Actions, GitLab CI and Jenkins support with SARIF output and fail thresholds?
- Supply chain: Does it produce SPDX or CycloneDX SBOMs and Cosign attestations, and support admission checks?
- Lifecycle: Does it rescan registries continuously and add runtime context to rank findings?
- Exceptions: Can you record expiring, owned waivers instead of permanent ignores?
Does runtime scanning replace build-time scanning? No. Build-time gates stop known-bad images from shipping, and runtime context decides which of the remaining findings to fix first.
The strongest programs treat container image scanning as one control in a chain. Scan at build, sign what passed, verify at admission, rescan stored SBOMs as advisories change, and let runtime evidence decide what your team fixes this week.
FAQ
What is container image scanning?
Container image scanning is the automated analysis of a container image’s layers, OS packages, application dependencies, binaries, and build configuration against vulnerability databases. It helps find known CVEs, embedded secrets, and risky settings before an image runs in production.
How does container image scanning work?
A scanner pulls the image, unpacks and flattens its layers, detects the operating system, enumerates OS and language packages, normalizes component identifiers, and compares package versions against advisory sources such as NVD, distro trackers, GHSA, and OSV. It then filters results using rules like distro status, VEX statements, and ignore lists before producing a report.
Why are distro advisories often more accurate than raw NVD matching?
Distro advisories account for backported fixes, where vendors patch an older package version without changing the upstream version number. NVD-only matching can wrongly flag those packages as vulnerable, while distro-specific feeds provide more accurate fixed-version and not-affected status for that release.
Can container image scanning tell which vulnerabilities actually matter in production?
Not by itself. Image scanning is static, so it shows what is installed, not what is loaded, reachable, internet-exposed, or under attack. Runtime monitoring adds that missing context and is the most reliable way to prioritize a large CVE backlog.
Why should teams store an SBOM for every image?
An SBOM preserves the package inventory for each released image, so teams can recheck stored images against newly disclosed CVEs without pulling and unpacking them again. That makes rescanning much faster when a major vulnerability is announced.
