docker.recipes
Security8 min read

Security Scanning Docker Images

Find vulnerabilities in your container images using Trivy, Docker Scout, and integrate scanning into your workflow.

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 Trivy
2# macOS
3brew install trivy
4
5# Linux (Debian/Ubuntu)
6sudo apt-get install wget apt-transport-https gnupg lsb-release
7wget -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.list
9sudo apt-get update && sudo apt-get install trivy
10
11# Scan an image
12trivy image nginx:latest
13
14# Scan with severity filter
15trivy image --severity HIGH,CRITICAL nginx:latest
16
17# Scan local image (not from registry)
18trivy image myapp:local
19
20# Output as JSON for CI/CD
21trivy image -f json -o results.json nginx:latest
22
23# Scan a Dockerfile
24trivy config Dockerfile

03Scanning 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.yml
2#!/bin/bash
3
4# Extract images from compose file
5images=$(docker compose config --images)
6
7echo "Scanning all images in docker-compose.yml..."
8echo "============================================"
9
10for image in $images; do
11 echo ""
12 echo "Scanning: $image"
13 echo "--------------------------------------------"
14 trivy image --severity HIGH,CRITICAL "$image"
15done
16
17# Or create a full report
18for image in $images; do
19 safe_name=$(echo "$image" | tr ':/' '_')
20 trivy image -f json -o "scan_$safe_name.json" "$image"
21done

Run 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 Desktop
2# For CLI-only, install the plugin
3
4# Scan an image
5docker scout cves nginx:latest
6
7# Quick overview
8docker scout quickview nginx:latest
9
10# Compare two images (great for updates)
11docker scout compare nginx:1.24 nginx:1.25
12
13# Check recommendations
14docker scout recommendations nginx:latest
15
16# Scan local images
17docker scout cves local://myapp:latest

05Interpreting 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 vulnerabilities
2trivy image --severity CRITICAL,HIGH myapp:latest
3
4# Ignore unfixed vulnerabilities (no patch available)
5trivy image --ignore-unfixed myapp:latest
6
7# Use ignore file for false positives
8# .trivyignore
9# CVE-2023-12345
10trivy image --ignorefile .trivyignore myapp:latest
11
12# Show detailed vulnerability info
13trivy image --format table myapp:latest

Focus 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 example
2name: Security Scan
3
4on:
5 push:
6 branches: [main]
7 pull_request:
8 schedule:
9 - cron: '0 6 * * *' # Daily at 6 AM
10
11jobs:
12 scan:
13 runs-on: ubuntu-latest
14 steps:
15 - uses: actions/checkout@v4
16
17 - name: Build image
18 run: docker build -t myapp:${{ github.sha }} .
19
20 - name: Run Trivy scan
21 uses: aquasecurity/trivy-action@master
22 with:
23 image-ref: myapp:${{ github.sha }}
24 format: sarif
25 output: trivy-results.sarif
26 severity: CRITICAL,HIGH
27 exit-code: 1 # Fail on vulnerabilities
28
29 - name: Upload results
30 uses: github/codeql-action/upload-sarif@v2
31 if: always()
32 with:
33 sarif_file: trivy-results.sarif

Set 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 images
2FROM alpine:3.19 # ~5MB, minimal packages
3FROM gcr.io/distroless/static # ~2MB, no shell
4FROM scratch # 0MB, for static binaries
5
6# Multi-stage builds
7FROM node:20 AS builder
8WORKDIR /app
9COPY package*.json ./
10RUN npm ci
11COPY . .
12RUN npm run build
13
14FROM node:20-alpine AS runtime
15WORKDIR /app
16COPY --from=builder /app/dist ./dist
17COPY --from=builder /app/node_modules ./node_modules
18USER node
19CMD ["node", "dist/index.js"]
20
21# Compare image sizes and vulnerabilities
22docker images | grep myapp
23trivy image myapp:full # Many vulnerabilities
24trivy image myapp:alpine # Fewer vulnerabilities
25trivy image myapp:distroless # Minimal vulnerabilities

Alpine-based images have far fewer vulnerabilities than Debian/Ubuntu-based images. Distroless images have even fewer.