# Image uses multi-stage builds to minimize final layer size

- **Pattern:** `ab-001633` (`infrastructure-hardening.container-image-security.multi-stage-build`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001633
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001633)

## Why it matters

Single-stage Docker builds include compilers, package managers, build caches, test fixtures, and development dependencies in the final production image. Every extra binary is an additional attack surface — a compromised container with build tools installed can compile exploits, exfiltrate files, or pivot to other services more easily. NIST 800-53 CM-7 requires systems to provide only essential capabilities. CIS Docker 4.9 explicitly requires minimal image construction. Images over 500 MB also inflate registry storage costs and slow deployment velocity at scale.

## Severity rationale

High because bloated images increase attack surface by including compilers and dev tools that have no role in production and enable post-compromise lateral movement.

## Remediation

Separate build and runtime stages in `Dockerfile` to exclude all build tooling from the final image:

```dockerfile
# Stage 1: compile
FROM node:20 AS builder
WORKDIR /build
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: runtime only
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /build/dist ./dist
COPY --from=builder /build/node_modules ./node_modules
USER 1001
CMD ["node", "dist/index.js"]
```

For Go or compiled binaries, the final stage can be `FROM scratch` or `FROM gcr.io/distroless/static`, reducing runtime images to under 20 MB.

## Detection

- **ID:** `multi-stage-build`
- **Severity:** `high`
- **What to look for:** Count all FROM statements in each Dockerfile to determine the number of build stages. For each Dockerfile, classify whether it uses multi-stage builds (at least 2 FROM statements with COPY --from between stages). Verify that build dependencies are not included in the final image and estimate final image size — production images should be under 500 MB.
- **Pass criteria:** Every Dockerfile uses multi-stage builds where applicable (at least 2 FROM statements: a builder stage for compilation and a minimal final stage). Final image size is estimated under 500 MB for application containers, and build artifacts and development dependencies are excluded from the final layer. Report: "X of Y Dockerfiles use multi-stage builds."
- **Fail criteria:** Any Dockerfile has a single stage with all dependencies (build tools, test files, source code) included in the final image, or final image exceeds 500 MB for an application container.
- **Skip (N/A) when:** The project does not have a Dockerfile, or the application legitimately requires a large runtime environment.
- **Detail on fail:** Quote the Dockerfile structure. Example: `"Dockerfile is single-stage. Final image includes build dependencies (gcc, make, etc.) and source code. Estimated size: 800 MB"` or `"Only the builder stage is used; no COPY --from final stage detected"`
- **Remediation:** Multi-stage builds reduce image size by ~80%, lowering attack surface and deployment time:

  ```dockerfile
  # Stage 1: build
  FROM node:20 AS builder
  WORKDIR /build
  COPY package.json package-lock.json ./
  RUN npm ci --only=production

  # Stage 2: runtime (minimal)
  FROM node:20-alpine
  WORKDIR /app
  COPY --from=builder /build/node_modules ./node_modules
  COPY app.js .
  USER 1001
  CMD ["node", "app.js"]
  ```

  This reduces a Node.js app from ~1GB (full image) to ~150MB (distroless + minimal dependencies).

## External references

- nist CM-7
- external CIS-Docker-4.9 — https://www.cisecurity.org/benchmark/docker
- iso-25010 security-maintainability

Taxons: operational-readiness

HTML version: https://auditbuffet.com/patterns/ab-001633
