Docker 1 min read 1

Your Dockerfile Is Probably Three Times Bigger Than It Needs to Be

Layer order, multi-stage builds and the cache behaviours nobody explains until your CI queue is forty minutes deep.

Your Dockerfile Is Probably Three Times Bigger Than It Needs to Be
Docker · October 31, 2026

Open a random production Dockerfile and you will usually find the same three sins: copying the whole repository first, installing dependencies after the source, and shipping the build toolchain to production.

Each one is small. Together they turn a 90 MB image into 1.4 GB and a 40-second rebuild into a nine-minute one.

Layers are a cache, and order is everything

Docker caches each layer keyed on the instruction and the files it touches. Change anything above a layer and every layer below it is rebuilt. So put the things that change least at the top.

# Bad: any source edit re-installs every dependency
COPY . /app
RUN npm ci

# Good: dependencies rebuild only when the lockfile changes
COPY package.json package-lock.json ./
RUN npm ci
COPY . /app

That single reorder is usually the difference between a cache hit and a full install on every commit. The Docker docs cover the layer model in more depth than most tutorials bother to.

Multi-stage builds: stop shipping your compiler

The build stage needs the SDK, the compiler and every dev dependency. The runtime needs almost none of it. Multi-stage builds let you throw the first stage away:

FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]

The final image now contains the compiled output and production dependencies — nothing else. On a typical TypeScript service that is the difference between 1.1 GB and about 180 MB.

A checklist that pays for itself

  • Start from a specific tag, never latest.
  • Copy lockfiles before source.
  • Use npm ci, not npm install, so the lockfile is authoritative.
  • Drop dev dependencies with a production install or a second stage.
  • Set NODE_ENV=production in the runtime stage only.
  • Run as a non-root user; it costs one line.
  • Put a .dockerignore next to the Dockerfile or you will copy .git and node_modules anyway.

None of this is clever. It is just ordering, and ordering is free.

PB
Written by

Parul Bedi

Part of the Azure Gravy writing team.

Up next

Zero-Downtime Rollbacks Are a Deployment Design Problem

Kubernetes November 3, 2026 2 min read 3 comments
Open post

Conversation

1 comment
  • MR Marco Rossi
    The lockfile-before-source reorder took our rebuild from 7 minutes to 40 seconds. I am slightly annoyed it was that easy.

Leave a comment

Replying to a comment.
Be kind. Markdown is not supported in comments.
Keep reading

Related posts

All posts