DeployU
Interviews / Cloud & DevOps / Security scan found critical vulnerabilities in your base image. How do you fix them?

Security scan found critical vulnerabilities in your base image. How do you fix them?

practical Security Interactive Quiz Code Examples

The Scenario

Your CI/CD pipeline’s security scan failed:

$ trivy image api:latest

api:latest (debian 11.6)
Total: 127 (CRITICAL: 5, HIGH: 23, MEDIUM: 67, LOW: 32)

┌────────────────┬──────────────────┬──────────┬───────────────────┐
    Library  Vulnerability Severity  Fixed Version
├────────────────┼──────────────────┼──────────┼───────────────────┤
 openssl CVE-2023-0286 CRITICAL 1.1.1t-1
 curl CVE-2023-23916 CRITICAL 7.88.1-1
 libc6 CVE-2023-0687 HIGH 2.36-9
 ... ... ... ...
└────────────────┴──────────────────┴──────────┴───────────────────┘

The security team is blocking the release. You need to remediate these vulnerabilities before the deployment deadline.

The Challenge

Fix the vulnerabilities and implement a process to prevent them in the future. Explain the trade-offs between different remediation strategies.

Wrong Approach

A junior engineer might try to apt-get update && upgrade in the Dockerfile, ignore the vulnerabilities because the app works fine, add exceptions for everything in the scanner, or rebuild without understanding what changed. These fail because apt-get upgrade isn't reproducible and may break things, ignoring vulns fails compliance, blanket exceptions defeat the purpose of scanning, and blind rebuilds don't guarantee fixes.

Right Approach

A senior engineer addresses vulnerabilities systematically: 1) Update the base image to latest patched version, 2) Use minimal base images to reduce attack surface, 3) Apply multi-stage builds to exclude build-time dependencies, 4) Implement vulnerability scanning in CI/CD with failure thresholds, 5) Set up automated base image updates. Each step reduces risk and improves security posture.

Step 1: Update Base Image

# BEFORE: Old image with vulnerabilities
FROM node:18

# AFTER: Specific patched version
FROM node:18.19.0-bookworm-slim

# Or even better - Alpine (fewer packages = fewer vulns)
FROM node:18.19.0-alpine3.19

Check for updates:

# Find latest tags
docker pull node:18-alpine
trivy image node:18-alpine

# Compare vulnerability counts
trivy image node:18          # ~150 vulns
trivy image node:18-slim     # ~50 vulns
trivy image node:18-alpine   # ~5 vulns

Step 2: Use Multi-Stage Build to Minimize Attack Surface

# Build stage - has build tools with potential vulns
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Production stage - minimal runtime only
FROM node:18.19.0-alpine3.19 AS production

# Don't include npm, yarn, or build tools
RUN apk add --no-cache tini

WORKDIR /app

# Copy only production dependencies
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./

USER node
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "dist/server.js"]

Step 3: Use Distroless for Maximum Security

FROM node:18 AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build

# Distroless has no shell, no package manager, minimal OS
FROM gcr.io/distroless/nodejs18-debian12

COPY --from=builder /app/dist /app/dist
COPY --from=builder /app/node_modules /app/node_modules

WORKDIR /app
USER nonroot
CMD ["dist/server.js"]

Distroless scan:

$ trivy image gcr.io/distroless/nodejs18-debian12
Total: 0 (CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0)

Step 4: CI/CD Integration

# GitHub Actions - fail on critical vulnerabilities
name: Security Scan

on: [push, pull_request]

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

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

      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: myapp:${{ github.sha }}
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL,HIGH'
          exit-code: '1'  # Fail pipeline on vulnerabilities

      - name: Upload Trivy scan results
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: 'trivy-results.sarif'

Step 5: Automated Base Image Updates

# Renovate config - auto-update Dockerfile base images
# renovate.json
{
  "extends": ["config:base"],
  "dockerfile": {
    "enabled": true,
    "fileMatch": ["Dockerfile$", "Dockerfile\\.[a-z]+$"]
  },
  "packageRules": [
    {
      "matchDatasources": ["docker"],
      "matchUpdateTypes": ["patch", "minor"],
      "automerge": true
    }
  ]
}

Vulnerability Remediation Strategies

StrategyEffortReductionTrade-offs
Update base image tagLow20-50%May introduce breaking changes
Switch to slim variantLow50-70%Some packages missing
Switch to AlpineMedium80-90%musl libc compatibility issues
Use distrolessHigh95%+No shell for debugging
Multi-stage buildsMedium60-80%Longer Dockerfile

Scanning Tools Comparison

# Trivy (recommended - fast, accurate)
trivy image myapp:latest

# Docker Scout (integrated with Docker Desktop)
docker scout cves myapp:latest

# Grype (Anchore)
grype myapp:latest

# Snyk
snyk container test myapp:latest

Setting Vulnerability Thresholds

# .trivyignore - accept known risks
# CVE-2023-XXXX  # Documented: not exploitable in our context
CVE-2023-12345  # False positive: feature not used

# trivy.yaml - policy configuration
severity:
  - CRITICAL
  - HIGH
ignore-unfixed: true

Practice Question

Which base image would typically have the fewest vulnerabilities for a Node.js application?