What a container vulnerability scanner actually looks at
A Docker image vulnerability scanner extracts a software inventory (similar to an SBOM) from every layer of the image, then cross-references each package against vulnerability databases (NVD, OSV, Alpine secdb, Debian Security Tracker, etc.).
The scanner typically identifies vulnerabilities in:
- OS packages:
apt/dpkgpackages on Debian/Ubuntu,apkon Alpine,rpmon RHEL/CentOS/Amazon Linux. - Language runtime packages: npm, PyPI, Gem, Maven, Go modules β detected from lockfiles,
node_modules/, or site-packages directories inside the image. - Binary files: Statically compiled binaries with detectable version information.
Alpine vs Debian base images: Alpine-based images typically have far fewer OS packages (and therefore fewer CVEs) than Debian/Ubuntu images. Switching from python:3.12 to python:3.12-alpine often reduces OS-level CVE counts by 80%.
Trivy β the recommended starting point
Trivy (by Aqua Security) is the most widely adopted open-source container scanner. It requires no daemon, no registry credentials for local images, and produces output in multiple formats (table, JSON, SARIF). Installation is a single binary.
# Install (macOS/Linux) $ brew install trivy # or: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh # Scan a local image $ trivy image myapp:latest # Scan and show only fixable HIGH/CRITICAL CVEs $ trivy image \ --ignore-unfixed \ --severity HIGH,CRITICAL \ myapp:latest # Scan a remote image from Docker Hub $ trivy image nginx:1.25 # Output as JSON for automation $ trivy image --format json --output results.json myapp:latest # Scan OS packages AND language packages together $ trivy image --scanners vuln,secret,misconfig myapp:latest
Sample output (truncated):
myapp:latest (debian 12.4) ========================== Total: 23 (HIGH: 8, CRITICAL: 2) ββββββββββββββββ¬βββββββββββββββββ¬βββββββββββ¬βββββββββββββββββββ¬βββββββββββββββββ β Library β Vulnerability β Severity β Installed Versionβ Fixed Version β ββββββββββββββββΌβββββββββββββββββΌβββββββββββΌβββββββββββββββββββΌβββββββββββββββββ€ β openssl β CVE-2023-5678 β CRITICAL β 3.0.11-1 β 3.0.13-1 β β libcurl4 β CVE-2023-38545 β HIGH β 7.88.1-10 β 7.88.1-10+deb12β β ... β ... β ... β ... β ... β ββββββββββββββββ΄βββββββββββββββββ΄βββββββββββ΄βββββββββββββββββββ΄βββββββββββββββββ
Grype + Syft β SBOM-first scanning
Grype (by Anchore) pairs with Syft (SBOM generator) to give you a two-step workflow: generate a Software Bill of Materials once, then scan it repeatedly against updated vulnerability databases without re-inspecting the image.
# Install $ curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin $ curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin # Direct image scan $ grype myapp:latest # Two-step: generate SBOM, then scan (store SBOM for re-use) $ syft myapp:latest -o cyclonedx-json > sbom.cdx.json $ grype sbom:./sbom.cdx.json --fail-on high # Fail CI if any HIGH/CRITICAL with a fix available $ grype myapp:latest --only-fixed --fail-on high
Docker Scout β built into Docker Desktop
Docker Scout is Docker's native vulnerability scanner, integrated directly into Docker Desktop and the Docker CLI since Docker 24. If your team already uses Docker Desktop, it requires zero additional setup.
# Quick vulnerability overview $ docker scout quickview myapp:latest # Detailed CVE list $ docker scout cves myapp:latest # Show only fixable critical/high CVEs $ docker scout cves --only-fixed --only-severity critical,high myapp:latest # Compare two image versions (useful after base image update) $ docker scout compare myapp:v1.0 myapp:v1.1
Tool comparison
| Tool | OS packages | Language packages | Secrets scan | IaC scan | SBOM output | Price |
|---|---|---|---|---|---|---|
| Trivy | β | β | β | β | β | Free / OSS |
| Grype | β | β | β | β | Via Syft | Free / OSS |
| Docker Scout | β | β | β | β | β | Free tier available |
| Snyk Container | β | β | β | β | β | Paid (free tier) |
| AquilaX | β | β | β | β | β | Free trial available |
CI/CD gate: block vulnerable images before push
name: Container Security Scan on: [push, pull_request] jobs: scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Build image run: docker build -t myapp:ci . - name: Trivy vulnerability scan run: | curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh trivy image \ --exit-code 1 \ --ignore-unfixed \ --severity CRITICAL,HIGH \ myapp:ci # Image only pushed if scan passes - name: Push to registry if: github.ref == 'refs/heads/main' run: docker push myapp:latest
Reducing your vulnerability count at the source
The best way to deal with container vulnerabilities is to have fewer packages to scan:
- Use minimal base images:
alpine,distroless, orscratch. A distroless Python image has no shell, no package manager, and no system utilities β the attack surface shrinks dramatically. - Multi-stage builds: Build in a full image, copy only the compiled artefact into a minimal final image. Build tools (gcc, make, pip) don't appear in the final image.
- Pin base image versions with digests:
FROM python:3.12-slim@sha256:abc123...prevents base image drift introducing new vulnerabilities silently. - Keep base images updated: Most OS CVEs are fixed in updated base images. Rebuild images weekly even if your application code hasn't changed.
Scan container images automatically in your pipeline
AquilaX scans Docker images for CVEs, secrets, misconfigurations, and malware β with a single integration step and prioritised, actionable results.
See container scanning β