CloudOpsGuide
devsecops

Docker Image Scanning: Security Pipeline

Intermediate
13 minutes
October 2026
CloudOpsGuide Team

Docker Image Scanning: Security Pipeline

Integrate container image scanning into CI/CD pipelines with Trivy, Grype, and Docker Scout — catch vulnerabilities before deployment.

Table of Contents

Why Scan Images

Container images layer on each other — your app image inherits every vulnerability in the base image plus whatever your layers add:

  • nginx:alpine might have 20 known CVEs
  • Your node:20 base might have 50
  • npm install might pull in vulnerable packages
  • OS packages (apt install) add more attack surface

Trivy — Fast and Complete

Basic Scan

# Scan an image
trivy image myapp:latest

# Scan for specific severities only
trivy image myapp:latest --severity HIGH,CRITICAL

# Output as JSON for parsing
trivy image myapp:latest --format json -o trivy-report.json

Scan in CI

# Fail pipeline on HIGH/CRITICAL
trivy image myapp:latest --severity HIGH,CRITICAL --exit-code 1

# Scan and generate HTML report
trivy image myapp:latest --format template --template '@contrib/html.tpl' -o report.html

Scan SBOM

# Generate SBOM first
trivy image --format cyclonedx -o sbom.json myapp:latest

# Or scan an existing SBOM
trivy sbom sbom.json

Grype — SBOM-Focused

# Scan image
grype myapp:latest

# Fail on high severity
grype myapp:latest --fail-on high

# Scan from SBOM
syft myapp:latest -o syft-json > sbom.json
grype sbom:sbom.json

Docker Scout — Built-In

# Scout (built into Docker Desktop/CLI)
docker scout cves myapp:latest

# Compare with base image
docker scout compare --to nginx:alpine myapp:latest

# Get recommendations
docker scout recommendations myapp:latest

CI/CD Integration

Jenkins

stage('Build & Scan') {
    steps {
        sh 'docker build -t myapp:${BUILD_NUMBER} .'
        sh '''
            trivy image myapp:${BUILD_NUMBER} \
              --severity HIGH,CRITICAL \
              --exit-code 1 \
              --format json -o trivy-${BUILD_NUMBER}.json
        '''
    }
    post {
        always {
            archiveArtifacts "trivy-*.json"
        }
    }
}

GitHub Actions

- name: Build image
  run: docker build -t myapp:${{ github.sha }} .

- name: Scan with Trivy
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: myapp:${{ github.sha }}
    severity: CRITICAL,HIGH
    exit-code: '1'
    format: 'sarif'
    output: 'trivy.sarif'

- name: Upload to GitHub Security
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: 'trivy.sarif'

GitLab CI

scan:
  image: aquasec/trivy:latest
  script:
    - trivy image $IMAGE_TAG --severity HIGH,CRITICAL --exit-code 1

Reducing Image Vulnerabilities

1. Minimal Base Images

# Before — full Ubuntu image, hundreds of packages
FROM ubuntu:22.04
RUN apt-get install -y nodejs npm

# After — minimal Alpine or distroless
FROM node:20-alpine
# or even better: FROM gcr.io/distroless/nodejs20

Vulnerability reduction: node:20 (~800 CVEs) → node:20-alpine (~20 CVEs) → distroless (~5 CVEs)

2. Multi-Stage Builds

# Build stage — has dev tools (attack surface not in final image)
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Final stage — only production deps
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
EXPOSE 3000
CMD ["node", "dist/index.js"]

3. Pin Versions

# Before — unpinned, updates break things
FROM node:latest
RUN npm install express

# After — pinned, reproducible
FROM node:20.18.0-alpine3.20
RUN npm install express@4.21.0

4. Clean Package Cache

# apt-get cache adds ~50MB and stale package lists
RUN apt-get update && apt-get install -y curl \
    && rm -rf /var/lib/apt/lists/*

# apk --no-cache
RUN apk add --no-cache curl

5. Remove Build Tools After Compiling

# gcc/make are needed for native modules but not at runtime
RUN apk add --no-cache --virtual .build gcc make python3 \
    && npm install \
    && apk del .build

Common Fixes

"Vulnerability found in os package"

# Update the base image — pull latest
docker pull node:20-alpine
docker build --no-cache -t myapp:latest .

"Vulnerability found in npm package"

# Check which package has the CVE
trivy image myapp:latest --severity CRITICAL | grep "npm"
# Update in package.json, rebuild

"Vulnerability found in golang binary"

# Rebuild with updated Go toolchain
docker build --build-arg GO_VERSION=1.23 -t myapp:latest .

Scanning Strategy

PR opened ──► SAST + dep scan (fast, catch code issues)
Merge to main ──► Build + Trivy scan (catch image issues)
Deploy to staging ──► DAST scan (catch runtime issues)
Deploy to prod ──► Runtime monitoring (catch live issues)

Related Articles


Last Updated: October 2026
Author: CloudOpsGuide Team
Difficulty: Intermediate
Estimated Reading Time: 13 minutes