01Why Scan Container Images?
Container images can contain vulnerabilities from:
• **Base OS packages** - Outdated system libraries
• **Application dependencies** - npm, pip, gem packages
• **Your code** - Misconfigurations, secrets
**Scanning helps you:**
• Find known vulnerabilities (CVEs)
• Meet compliance requirements
• Prioritize updates based on severity
• Block vulnerable images from deploying
The best time to scan is before deployment, but regular scanning catches newly discovered vulnerabilities.
Most vulnerabilities are in base images. Using slim/distroless images dramatically reduces your attack surface.
02Scanning with Trivy
Trivy is a popular open-source scanner that's fast, accurate, and easy to use. It scans images, filesystems, and Git repositories.
1# Install Trivy2# macOS3brew install trivy45# Linux (Debian/Ubuntu)6sudo apt-get install wget apt-transport-https gnupg lsb-release7wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -8echo deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main | sudo tee -a /etc/apt/sources.list.d/trivy.list9sudo apt-get update && sudo apt-get install trivy1011# Scan an image12trivy image nginx:latest1314# Scan with severity filter15trivy image --severity HIGH,CRITICAL nginx:latest1617# Scan local image (not from registry)18trivy image myapp:local1920# Output as JSON for CI/CD21trivy image -f json -o results.json nginx:latest2223# Scan a Dockerfile24trivy config Dockerfile03Scanning Your Docker Compose Stack
Scan all images in your Compose file to get a full picture of your stack's security posture.
1# Script to scan all images in docker-compose.yml2#!/bin/bash34# Extract images from compose file5images=$(docker compose config --images)67echo "Scanning all images in docker-compose.yml..."8echo "============================================"910for image in $images; do11 echo ""12 echo "Scanning: $image"13 echo "--------------------------------------------"14 trivy image --severity HIGH,CRITICAL "$image"15done1617# Or create a full report18for image in $images; do19 safe_name=$(echo "$image" | tr ':/' '_')20 trivy image -f json -o "scan_$safe_name.json" "$image"21doneRun scans regularly with cron or scheduled CI jobs. New CVEs are discovered daily.
04Using Docker Scout
Docker Scout is Docker's built-in scanning tool, integrated into Docker Desktop and the CLI.
1# Docker Scout is included with Docker Desktop2# For CLI-only, install the plugin34# Scan an image5docker scout cves nginx:latest67# Quick overview8docker scout quickview nginx:latest910# Compare two images (great for updates)11docker scout compare nginx:1.24 nginx:1.251213# Check recommendations14docker scout recommendations nginx:latest1516# Scan local images17docker scout cves local://myapp:latest05Interpreting Scan Results
Not all vulnerabilities need immediate action. Here's how to prioritize:
**Critical/High severity:**
Fix immediately if exploitable in your context.
**Medium severity:**
Schedule fixes in regular maintenance.
**Low severity:**
Address during updates or if specifically relevant.
**Consider exploitability:**
• Is the vulnerable code reachable?
• Is your container exposed to attackers?
• Does your configuration mitigate the risk?
1# Get only critical and high vulnerabilities2trivy image --severity CRITICAL,HIGH myapp:latest34# Ignore unfixed vulnerabilities (no patch available)5trivy image --ignore-unfixed myapp:latest67# Use ignore file for false positives8# .trivyignore9# CVE-2023-1234510trivy image --ignorefile .trivyignore myapp:latest1112# Show detailed vulnerability info13trivy image --format table myapp:latestFocus on vulnerabilities with available fixes first. Unfixed CVEs may need mitigation through other means.
06CI/CD Integration
Integrate scanning into your pipeline to catch vulnerabilities before deployment.
1# GitHub Actions example2name: Security Scan34on: 5 push: 6 branches: [main]7 pull_request: 8 schedule: 9 - cron: '0 6 * * *' # Daily at 6 AM1011jobs: 12 scan: 13 runs-on: ubuntu-latest14 steps: 15 - uses: actions/checkout@v41617 - name: Build image18 run: docker build -t myapp:${{ github.sha }} .1920 - name: Run Trivy scan21 uses: aquasecurity/trivy-action@master22 with: 23 image-ref: myapp:${{ github.sha }}24 format: sarif25 output: trivy-results.sarif26 severity: CRITICAL,HIGH27 exit-code: 1 # Fail on vulnerabilities2829 - name: Upload results30 uses: github/codeql-action/upload-sarif@v231 if: always()32 with: 33 sarif_file: trivy-results.sarifSet exit-code: 1 to fail builds with vulnerabilities. Start with CRITICAL only, then add HIGH once you've addressed existing issues.
07Reducing Your Attack Surface
The best way to have fewer vulnerabilities is to have less code. Here's how to minimize your images:
1# Use minimal base images2FROM alpine:3.19 # ~5MB, minimal packages3FROM gcr.io/distroless/static # ~2MB, no shell4FROM scratch # 0MB, for static binaries56# Multi-stage builds7FROM node:20 AS builder8WORKDIR /app9COPY package*.json ./10RUN npm ci11COPY . .12RUN npm run build1314FROM node:20-alpine AS runtime15WORKDIR /app16COPY --from=builder /app/dist ./dist17COPY --from=builder /app/node_modules ./node_modules18USER node19CMD ["node", "dist/index.js"]2021# Compare image sizes and vulnerabilities22docker images | grep myapp23trivy image myapp:full # Many vulnerabilities24trivy image myapp:alpine # Fewer vulnerabilities25trivy image myapp:distroless # Minimal vulnerabilitiesAlpine-based images have far fewer vulnerabilities than Debian/Ubuntu-based images. Distroless images have even fewer.