Vulnerability Scanning
Docker image scanning: Trivy, Grype, Snyk, Docker Scout. Catch CVEs in dependencies before they ship.
Docker — image scanning
EXAMPLE
# ===== Why scan =====
# Container images embed Linux distro + your deps + your app.
# Every layer can have CVEs. Scanners check each layer against vulnerability databases.
# ===== Trivy (open-source, popular) =====
# Install:
brew install trivy
# or:
docker run aquasec/trivy image alpine:latest
# Scan an image:
trivy image nginx:1.25-alpine
# Scan a file system (build directory):
trivy fs ./
# Scan a Dockerfile:
trivy config Dockerfile
# Output formats:
trivy image -f json -o report.json nginx:latest
trivy image -f sarif -o report.sarif nginx:latest
# Fail CI on HIGH+ findings:
trivy image --severity HIGH,CRITICAL --exit-code 1 myapp:dev
# ===== Grype (Anchore, also open-source) =====
brew install grype
grype nginx:latest
grype sbom:./sbom.json # scan from SBOM
# ===== Docker Scout (built in to Docker Desktop) =====
docker scout cves nginx:latest
docker scout recommendations nginx:latest
# ===== Snyk =====
brew install snyk
snyk container test nginx:latest
snyk container monitor nginx:latest
# ===== In CI (GitHub Actions) =====
name: scan
on: [pull_request]
jobs:
trivy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
severity: 'HIGH,CRITICAL'
exit-code: '1'
format: 'sarif'
output: trivy.sarif
- uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: trivy.sarif }
# ===== Reduce attack surface =====
# 1. Smaller base images: alpine, distroless, scratch (for static binaries)
# 2. Multi-stage build: leave build tools in builder stage; runtime is minimal
# 3. Pin base by digest: FROM node:20-alpine@sha256:...
# 4. Update regularly: monthly base bumps; CI gates on scan results
# 5. Non-root user: USER node in Dockerfile
# ===== Multi-stage example =====
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER nonroot
CMD ["dist/server.js"]
# Distroless removes shells + package managers; tiny + low CVE surface.
# ===== SBOM (Software Bill of Materials) =====
trivy image --format cyclonedx -o sbom.json nginx:latest
syft nginx:latest -o cyclonedx-json > sbom.json
# Ship the SBOM with each release; auditors love them.
# ===== Patterns =====
# - Scan in CI on every PR
# - Fail on HIGH+ findings; ticket MEDIUM
# - SBOM published with each image
# - Distroless / minimal base in production
# ===== Pitfalls =====
# - Scanning only ON RELEASE -> CVEs ship to prod
# - Ignoring CVEs in 'system' libraries you don't use directly (still a risk)
# - Old vulnerability DB cached -> always update
# - Allowlists without expiry / reviewer -> findings rot
Why it matters
Scan images in CI; fail on HIGH+ findings. Trivy / Grype / Scout / Snyk all work. Pair with minimal base (alpine, distroless), multi-stage builds, non-root user, and SBOM. The CVE story is hygiene, not a one-off audit.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
docker scout cves my-api:1.0 # Trivy is another popular open-source scanner.Try it Yourself »
Discussion
Loading…