Skip to content

🐳 Multi-Stage Docker Builds

So far you’ve been using a single-stage Dockerfile β€” one big image that includes everything: dev tools, all of npm, and your source code. That’s fine for learning, but in production you want your image to be lean and fast. That’s where multi-stage builds come in. πŸš€


πŸ“¦ Single-Stage vs Multi-Stage

Think of it like packing for a trip:

  • 🧳 Single-stage: You pack your entire house β€” clothes, furniture, tools β€” into one suitcase. It works, but it’s huge.
  • πŸŽ’ Multi-stage: You only pack what you actually need for the trip. Everything else stays home.
Single-StageMulti-Stage
Image size❌ Large (includes dev tools)βœ… Small (only what runs the app)
Build speedSlower on CI/CDFaster deploys
SecurityMore attack surfaceMinimal surface area
Best forDevelopment & learningProduction πŸš€

πŸ” Your Current (Single-Stage) Dockerfile

FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
ENV PORT=3000
CMD ["node", "index.js"]

This installs all packages (including any dev dependencies) and ships them in the final image. Works β€” but not ideal for production.


πŸš€ The Multi-Stage Dockerfile

# ── Stage 1: Builder ──────────────────────────────────────────
# This stage installs dependencies and does any build work.
# It will NOT be included in the final image.
FROM node:22-alpine AS builder
WORKDIR /app
# Copy package files and install ALL dependencies
COPY package*.json ./
RUN npm install
# Copy the rest of the source code
COPY . .
# ── Stage 2: Production ───────────────────────────────────────
# This is the lean final image β€” only what's needed to RUN the app.
FROM node:22-alpine AS production
WORKDIR /app
# Copy only the necessary files from the builder stage
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/index.js ./
# Set the port
ENV PORT=3000
# Start the app
CMD ["node", "index.js"]

πŸ”‘ Key Things to Understand

  • AS builder β†’ Names the first stage. You can call it anything.
  • FROM node:22-alpine AS production β†’ Starts a brand new, clean image.
  • COPY --from=builder β†’ Pulls specific files from the builder stage into the final image. Everything else (the build tools, temp files, etc.) is left behind.
  • The final image only contains package.json, node_modules, and index.js β€” nothing more.

πŸ› οΈ Build & Run (Same Commands!)

Multi-stage builds are transparent β€” you use the exact same commands:

Build:

Terminal window
docker build -t express-app .

Run:

Terminal window
docker run -p 4000:3000 --rm express-app

Open: πŸ‘‰ http://localhost:4000 πŸŽ‰

Docker automatically handles the stages behind the scenes. You only get the final production image tagged as express-app.


πŸ“ See the Size Difference

After building, check your image size:

Terminal window
docker images express-app

Compare your old single-stage image vs the new multi-stage one β€” the difference can be significant once your app grows and has more dependencies.


βœ… Quick Summary

StepWhat it does
FROM ... AS builderStage 1 β€” installs everything, does build work
FROM ... AS productionStage 2 β€” fresh, clean image
COPY --from=builderCopies only what’s needed from Stage 1
docker build -t express-app .Builds just like before
docker run -p 4000:3000 express-appRuns the lean production image

Multi-stage builds are the standard way to ship Node.js apps in production. Small image = faster deploys, less storage, and fewer security risks. πŸ”