BlogKnowledge

Container Image Scanning: A Complete Guide to Securing Your Containers

Container images bundle your application code with an entire OS filesystem, system libraries, and runtime dependencies. A single image can contain hundreds of packages — each with its own vulnerability history. Container image scanning analyzes every layer to find what is vulnerable, misconfigured, or malicious before the container reaches production.

18 min readUpdated May 2026

What Is Container Image Scanning?

Container image scanning is the automated process of analyzing the contents of a container image — layer by layer — to identify security vulnerabilities, malware, hardcoded secrets, misconfigurations, and license compliance issues. Scanners decompose each image layer, catalog every installed package (both OS and application), match them against vulnerability databases, and generate a Software Bill of Materials (SBOM).

Scanning should happen at multiple points: when the image is built in CI, when it is pushed to a container registry, and continuously in the registry as new CVEs are published. Admission controllers in Kubernetes can enforce that only scanned, policy-compliant images are allowed to run.

What Container Scanners Detect

OS Package CVEs

Known vulnerabilities in OS packages like openssl, glibc, curl, and zlib. The most common finding category — a typical base image has 50–200 CVEs.

App Dependency Vulns

Vulnerabilities in language-specific packages (npm, pip, Maven, Go modules) installed inside the image.

Malware & Trojans

Cryptominers, backdoors, and trojanized binaries embedded in image layers. Detected via ClamAV and YARA signature matching.

Secrets in Layers

API keys, passwords, private keys, and tokens accidentally baked into image layers during build. Even deleted files persist in earlier layers.

Misconfigurations

Running as root, exposed sensitive ports, missing HEALTHCHECK, using latest tag, and missing USER directive in Dockerfile.

Outdated Base Images

Base images older than 90 days or using deprecated versions (node:10, python:2.7, centos:6) that no longer receive security patches.

How Container Scanning Works

1

Layer Decomposition

The scanner pulls and unpacks the image, analyzing each layer independently. This reveals files added, modified, or deleted at each build step.

2

Package Cataloging

OS packages (dpkg, rpm, apk) and language packages (package.json, requirements.txt, go.sum, pom.xml) are inventoried to build a complete SBOM.

3

Vulnerability Matching

Each package is matched against vulnerability databases — NVD, OSV, GitHub Advisory, Alpine SecDB, Debian Security Tracker, and vendor-specific databases.

4

Policy Evaluation

Findings are evaluated against organizational policies: CVSS thresholds, blocklisted packages, required base images, and compliance framework requirements.

Container Security Best Practices

Build Phase

Use minimal base images (distroless, Alpine, slim variants)
Pin base image digests, not tags — tags are mutable
Add USER directive — never run as root
Include HEALTHCHECK instruction
Use multi-stage builds to exclude build tools
Clean up package manager caches in the same layer

Deploy Phase

Scan images in CI — fail builds for critical CVEs
Re-scan images in registry continuously for new CVEs
Use admission controllers to block unscanned images
Generate and store SBOMs with each image version
Enforce image signing with Cosign or Notation
Set resource limits and read-only root filesystem

Where to Scan: Build, Registry, and Runtime

A common mistake is scanning at exactly one point in the lifecycle. Each stage catches problems the others cannot, and a mature program uses all three.

Scanning in CI

Scanning at build time gives developers immediate feedback while the Dockerfile is still open in their editor. A typical GitHub Actions step runs trivy image --exit-code 1 --severity CRITICAL,HIGH myapp:$SHA right after docker build, failing the pipeline before the image is ever pushed. The limitation: CI scanning is a point-in-time check. An image that passed cleanly on Monday can be affected by a critical CVE published on Wednesday.

Scanning in the Registry

Registry scanning solves the freshness problem. Because the vulnerability database changes daily while your images do not, continuous re-scanning of stored images (ECR enhanced scanning, Harbor with Trivy, or a platform scanner polling the registry) surfaces newly disclosed CVEs in images already running in production. When Log4Shell dropped, teams with registry scanning knew within hours exactly which images and which workloads were affected; teams with CI-only scanning had to rebuild everything just to find out.

Runtime Correlation

The final stage correlates scan results with what is actually running. A critical CVE in an image with zero running instances is a backlog item; the same CVE in a container serving internet traffic is an incident. eBPF-based runtime sensors go further by observing which packages and libraries are actually loaded into memory — in practice only a fraction of the packages in an image are ever executed, which lets you deprioritize vulnerabilities in code that never runs. TigerGate pairs its image scanner with an eBPF agent for exactly this reason: scan findings plus runtime context yields a much shorter, more honest priority list.

Base Image Strategy: The Highest-Leverage Fix

Most CVEs in a typical image come from the base layer, not from your application. Choosing a smaller base is the single highest-leverage change you can make. Moving a Node.js service from node:20 (a full Debian image with roughly 800 packages) to node:20-slim typically cuts findings by more than half; moving to a distroless or Chainguard-style image can reduce the CVE count to near zero, because there is no shell, no package manager, and almost no userland left to be vulnerable.

The trade-offs are real but manageable. Distroless images are harder to debug (no shell to kubectl exec into — use ephemeral debug containers instead), and Alpine's musl libc occasionally causes subtle incompatibilities with software built against glibc. Multi-stage builds resolve most of the friction: compile in a full-featured builder image, then copy only the binary and its runtime dependencies into the minimal final stage.

Equally important is establishing a small set of golden base images maintained by a platform team. Instead of every service choosing its own base, teams inherit from an internal image that is rebuilt weekly, patched centrally, and pinned by digest. When a base-layer CVE lands, one rebuild plus automated downstream rebuilds patches the entire fleet — rather than hunting through dozens of Dockerfiles.

Handling Scanner Noise Without Ignoring Real Risk

Teams that fail with container scanning usually fail the same way: they turn on a scanner, see 4,000 findings across their registry, and quietly stop looking at the dashboard. Avoiding that outcome requires deliberate policy, not more scanning.

Gate on fixable criticals only at first. Failing builds on every HIGH with no available patch teaches developers to ignore the gate. Start with 'critical severity AND a fix version exists', then tighten over time.
Respect vendor security trackers. Debian and Red Hat frequently mark upstream CVEs as not-affected or minor for their builds; a scanner that ignores distro analysis over-reports dramatically.
Use VEX documents and time-boxed ignores. Record 'not exploitable in our configuration' decisions in a reviewable file (e.g., a .trivyignore or OpenVEX statement) with an expiry date — never as permanent silent suppressions.
Deduplicate across layers of tooling. The same openssl CVE reported by CI scanning, registry scanning, and a Kubernetes agent is one problem, not three. Aggregate findings by package and image digest.
Track burn-down per team, not global totals. A single fleet-wide CVE count is demoralizing and unactionable; per-team, per-service trends drive actual fixes.

The goal is a scanning program developers trust: a red build should mean “this is genuinely dangerous and fixable right now,” and nothing else.

Enforcing Scan Policy in Kubernetes

Scanning without enforcement is advisory. Kubernetes admission control is where policy becomes mandatory: a validating admission webhook (Kyverno, OPA Gatekeeper, or a scanner-integrated controller) intercepts every pod creation and rejects images that violate policy before they are scheduled.

Effective admission policies are usually simple: require images from approved registries only, require a valid Cosign signature from your CI identity, reject images whose latest scan is older than a defined window or contains fixable critical CVEs, and reject workloads requesting privileged mode or writable root filesystems. Kyverno's verifyImages rule handles signature checks declaratively, and its audit mode lets you observe what would be blocked before enforcing — essential for rolling out policy in a busy cluster without breaking deployments.

The SBOM you generated at build time closes the loop here. Because the SBOM is attached to the image as a signed attestation, incident responders can answer “which running workloads contain this package?” with a query instead of a fleet-wide re-scan — the difference between a fifteen-minute answer and a multi-day scramble when the next Log4Shell-class event arrives.

Scan Container Images with TigerGate

TigerGate scans container images for vulnerabilities, malware, secrets, and misconfigurations — then monitors running containers with eBPF for runtime anomaly detection.