Skip to content

use-multi-stage

How multi-stage Docker builds keep compilers and build tools out of your production image and cut image size dramatically.

On this page

A single-stage Dockerfile ships everything the build needed: compilers, dev dependencies, source code, package-manager caches. None of that runs in production — it just rides along in every pull. This rule suggests splitting build and runtime into separate stages when a Dockerfile shows build activity but only one FROM.

What the rule catches

Dockerfile — build tools ship to production
FROM node:22
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
CMD ["node", "dist/server.js"]

Scanning this file reports:

ℹ INFO [docker-doctor/use-multi-stage]
  Only one build stage (FROM) was detected, but build instructions were found. Multi-stage builds can significantly reduce final image size.

Why it matters

The runtime image should contain the app and its runtime, nothing else. Multi-stage builds get you there without shell gymnastics: one stage has the full toolchain, the final stage starts from a clean base and COPY --from pulls in only the built artifacts. The payoff is smaller images (often by hundreds of megabytes), faster pulls and cold starts, and a much smaller attack surface — a compiler in a production container only ever helps an attacker.

How to fix it

Use multi-stage builds (multiple FROM statements) to separate build dependencies from the runtime image and reduce size.

Dockerfile — build stage discarded, runtime stays clean
FROM node:22 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
USER node
CMD ["node", "dist/server.js"]

Everything in the build stage — dev dependencies, source, caches — is discarded; only what the final stage explicitly copies survives. For compiled languages the gap is even larger: a Go or Rust binary can run FROM gcr.io/distroless/static, taking the image from gigabytes to tens of megabytes.

Rule details

  • Rule keydocker-doctor/use-multi-stage
  • Category — Performance
  • Default severityinfo
  • Applies to — Dockerfiles

Explain this rule from the CLI:

npx @docker-doctor/cli@latest rules explain docker-doctor/use-multi-stage

Change its severity — or turn it off — in your config file:

// docker-doctor.config.ts
export default {
  rules: {
    "docker-doctor/use-multi-stage": "off",
  },
};

Severity affects the health score: error findings cost more points than warning, and info costs the least.