Questions
Security scan found critical vulnerabilities in your base image. How do you fix them?
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.
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.
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.19Check 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 vulnsStep 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
| Strategy | Effort | Reduction | Trade-offs |
|---|---|---|---|
| Update base image tag | Low | 20-50% | May introduce breaking changes |
| Switch to slim variant | Low | 50-70% | Some packages missing |
| Switch to Alpine | Medium | 80-90% | musl libc compatibility issues |
| Use distroless | High | 95%+ | No shell for debugging |
| Multi-stage builds | Medium | 60-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?