Container vulnerability scanning is the single highest-ROI security control you can add to a CI/CD pipeline. A 2025 Snyk report found that 84% of container images in production contain at least one high or critical vulnerability, and 60% of those were introduced through base images that teams had not updated in over 90 days. Scanning at build time — before images reach production — closes that gap without adding meaningful latency to your pipeline.
This guide compares the four dominant scanners (Trivy, Grype, Snyk, and Anchore), provides production-ready GitHub Actions YAML, covers runtime scanning with Falco, walks through SBOM generation with Syft, and defines a remediation SLA framework your team can adopt immediately.
---
Scanner Comparison: Trivy vs Grype vs Snyk vs Anchore
The right scanner depends on your pipeline speed requirements, language ecosystem, licensing budget, and whether you need enterprise compliance reporting.
| Criterion | Trivy | Grype | Snyk | Anchore Enterprise |
| Scan speed (alpine:latest) | 4-8s | 6-12s | 15-30s | 20-45s |
| Scan speed (node:20-slim) | 12-20s | 18-30s | 35-60s | 40-90s |
| CVE database source | OSV + NVD + GitHub | Grype DB (OSV) | Snyk Intel | NVD + vendor feeds |
| Language package coverage | 20+ languages | 15+ languages | 30+ languages | 20+ languages |
| SBOM generation | Yes (CycloneDX, SPDX) | Yes (SPDX) | Yes (CycloneDX) | Yes (SPDX, CycloneDX) |
| License scanning | Yes | No | Yes | Yes |
| IaC scanning | Yes | No | Yes | No |
| Secret detection | Yes | No | Yes | No |
| CI/CD integration | GitHub Actions, GitLab, Jenkins, CircleCI | GitHub Actions, GitLab | GitHub Actions, GitLab, Jenkins, 20+ | GitHub Actions, GitLab, Jenkins |
| Pricing | Free (open source) | Free (open source) | Free tier + paid plans from $98/mo | Enterprise pricing (~$25K+/yr) |
| False positive rate | Low | Low | Medium | Low |
| Policy-as-code | Yes (OPA) | No | Yes | Yes |
| Compliance reports | No | No | Yes (SOC 2, ISO 27001) | Yes (PCI DSS, HIPAA, CIS) |
Recommendation by use case:
- Startups and open-source projects: Trivy — fastest, zero cost, broadest coverage
- Developer-first security: Snyk — IDE plugins, PR comments, actionable fix guidance
- Enterprise compliance: Anchore Enterprise — policy engines, audit trails, regulatory reports
- Lightweight supplemental scanning: Grype — pairs well with Trivy for cross-validation
---
GitHub Actions Integration: Production-Ready Trivy Workflow
The following workflow scans on every pull request and push to main, fails the build on critical vulnerabilities, and uploads results to GitHub Security tab via SARIF.
<h1>.github/workflows/container-scan.yml</h1>
name: Container Security Scan
on:
push:
branches: [main, staging]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-scan:
name: Build and Vulnerability Scan
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Build Docker image
run: |
docker build -t ${{ env.IMAGE_NAME }}:${{ github.sha }} .
# Trivy filesystem scan (pre-build, catches dependencies early)
- name: Trivy filesystem scan
uses: aquasecurity/trivy-action@master
with:
scan-type: fs
scan-ref: .
severity: HIGH,CRITICAL
exit-code: 0 # Warn only on filesystem scan
format: table
# Trivy image scan (post-build, catches base image CVEs)
- name: Trivy image scan
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.IMAGE_NAME }}:${{ github.sha }}
format: sarif
output: trivy-results.sarif
severity: CRITICAL,HIGH
exit-code: 1 # Fail build on CRITICAL
ignore-unfixed: true
vuln-type: os,library
- name: Upload SARIF to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
if: always() # Upload even on scan failure
with:
sarif_file: trivy-results.sarif
# Generate SBOM for audit trail
- name: Generate SBOM with Syft
run: |
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
syft ${{ env.IMAGE_NAME }}:${{ github.sha }} \
-o cyclonedx-json=sbom.cdx.json \
-o spdx-json=sbom.spdx.json
- name: Upload SBOM artifacts
uses: actions/upload-artifact@v4
with:
name: sbom-${{ github.sha }}
path: |
sbom.cdx.json
sbom.spdx.json
retention-days: 90
- name: Push image if scan passes
if: github.ref == 'refs/heads/main'
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
docker tag ${{ env.IMAGE_NAME }}:${{ github.sha }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
---
Base Image Selection: Security Criteria
The base image accounts for 60-80% of all container vulnerabilities. Choosing the right base image is the highest-leverage security decision you make before your application code runs.
| Base Image | Vuln Count (typical) | Size | Use Case | Notes |
| ubuntu:24.04 | 80-120 | 78 MB | General purpose | Large attack surface |
| debian:12-slim | 40-70 | 75 MB | General purpose | Better than ubuntu |
| alpine:3.20 | 0-5 | 7 MB | Production apps | musl libc compatibility check required |
| distroless/static | 0 | 2 MB | Go static binaries | No shell, no package manager |
| distroless/base | 0-2 | 20 MB | Dynamically linked apps | Minimal glibc only |
| distroless/nodejs22 | 3-8 | 110 MB | Node.js apps | No npm, no shell |
| chainguard/node | 0-2 | 95 MB | Node.js production | SBOM and provenance included |
| chainguard/python | 0-2 | 60 MB | Python production | Regularly rebuilt with patches |
Multi-stage build pattern (most secure approach):
<h1>Build stage: use full image for compilation</h1>
FROM node:22-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
<h1>Runtime stage: use minimal image</h1>
FROM gcr.io/distroless/nodejs22-debian12
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER nonroot
EXPOSE 3000
CMD ["dist/server.js"]
---
Runtime Scanning with Falco
Build-time scanning misses runtime attacks: container escapes, cryptomining injected via compromised npm packages, and lateral movement using legitimate binaries. Falco provides kernel-level behavioral detection for these threats.
<h1>falco-rules-custom.yaml — add to /etc/falco/rules.d/</h1>
<ul><li>rule: Unexpected Outbound Network Connection</li>
</ul> desc: Detects unexpected outbound connections from container
condition: >
outbound and container and
not fd.sip in (allowed_outbound_ips) and
not proc.name in (allowed_network_processes)
output: >
Unexpected outbound connection (user=%user.name command=%proc.cmdline
connection=%fd.name container=%container.name image=%container.image.repository)
priority: WARNING
tags: [network, container]
<ul><li>rule: Shell Spawned in Container</li>
</ul> desc: Detects shell execution inside a running container
condition: >
spawned_process and container and
proc.name in (shell_binaries) and
container.name != "debug-container"
output: >
Shell spawned in container (user=%user.name container=%container.name
image=%container.image.repository shell=%proc.name parent=%proc.pname)
priority: CRITICAL
tags: [shell, container, mitre_execution]
<ul><li>rule: Cryptominer Execution</li>
</ul> desc: Detects common cryptomining process patterns
condition: >
spawned_process and container and
(proc.name in (cryptominer_binaries) or
proc.cmdline contains "--mining-threads" or
proc.cmdline contains "stratum+tcp")
output: >
Cryptominer detected (command=%proc.cmdline container=%container.name)
priority: CRITICAL
tags: [cryptomining, container]
---
SBOM Generation with Syft
A Software Bill of Materials (SBOM) is a machine-readable inventory of every dependency in your container. When a new CVE drops (like Log4Shell), an SBOM lets you answer "are we affected?" in seconds rather than hours.
<h1>Install Syft</h1>
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
<h1>Generate SBOM in multiple formats</h1>
syft myapp:latest -o cyclonedx-json=sbom.cdx.json # For Dependency-Track
syft myapp:latest -o spdx-json=sbom.spdx.json # For legal/compliance
syft myapp:latest -o table # Human-readable summary
<h1>Cross-check SBOM against vulnerability database with Grype</h1>
grype sbom:sbom.spdx.json --fail-on high
<h1>Attest SBOM to image with cosign (supply chain integrity)</h1>
cosign attest --predicate sbom.cdx.json \
--type cyclonedx \
myapp:latest
---
Remediation SLA Framework
Scanning without enforced remediation timelines is theater. Define SLAs before your first critical CVE hits production.
Vulnerability Severity Response Table
| CVSS Score | Severity | SLA — Production | SLA — Staging | SLA — Dev | Immediate Action |
| 9.0-10.0 | Critical | 24 hours | 48 hours | 72 hours | Page on-call, freeze deployments |
| 7.0-8.9 | High | 7 days | 14 days | 30 days | Create P1 ticket, assign owner |
| 4.0-6.9 | Medium | 30 days | 60 days | 90 days | Add to sprint backlog |
| 0.1-3.9 | Low | 90 days | 90 days | Next quarter | Track in vulnerability register |
| N/A | Informational | Best effort | Best effort | Best effort | Log and review |
Enforcement automation:
<h1>.trivyignore — track accepted risks with expiration</h1>
<h1>Format: CVE-ID exp:YYYY-MM-DD reason:...</h1>
CVE-2023-44487 exp:2026-06-01 reason:mitigated-by-nginx-rate-limit
CVE-2024-21626 exp:2026-04-15 reason:non-exploitable-no-volume-mounts
Arxentra's managed container platform supports on-demand base image updates through a manual runtime image override, letting you patch OS-layer CVEs without rebuilding your full pipeline. See Arxentra container services for how the runtime image override integrates with your deployment pipeline, and review the platform security overview for compliance reporting options.
---
Pipeline Integration Checklist
- Scan on every PR (not just merge to main)
- Gate on CRITICAL severity with exit-code 1
- Upload SARIF to GitHub Security tab for developer visibility
- Generate and store SBOM per image SHA
- Set .trivyignore review cadence (monthly minimum)
- Enable Falco runtime rules in staging before production
- Run Syft + Grype cross-validation weekly on production images
- Review SLA compliance metrics monthly in security review
Container security is a continuous process, not a one-time gate. Teams that automate scanning, enforce SLAs, and layer runtime detection reduce mean time to remediation by 67% compared to manual processes (Snyk State of DevSecOps, 2025). Start with Trivy in CI, add Falco in production, and generate SBOMs from day one — your future self auditing a critical CVE will thank you.