π³ 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-Stage | Multi-Stage | |
|---|---|---|
| Image size | β Large (includes dev tools) | β Small (only what runs the app) |
| Build speed | Slower on CI/CD | Faster deploys |
| Security | More attack surface | Minimal surface area |
| Best for | Development & learning | Production π |
π 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 dependenciesCOPY package*.json ./RUN npm install
# Copy the rest of the source codeCOPY . .
# ββ 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 stageCOPY --from=builder /app/package*.json ./COPY --from=builder /app/node_modules ./node_modulesCOPY --from=builder /app/index.js ./
# Set the portENV PORT=3000
# Start the appCMD ["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, andindex.jsβ nothing more.
π οΈ Build & Run (Same Commands!)
Multi-stage builds are transparent β you use the exact same commands:
Build:
docker build -t express-app .Run:
docker run -p 4000:3000 --rm express-appOpen: π 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:
docker images express-appCompare 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
| Step | What it does |
|---|---|
FROM ... AS builder | Stage 1 β installs everything, does build work |
FROM ... AS production | Stage 2 β fresh, clean image |
COPY --from=builder | Copies only whatβs needed from Stage 1 |
docker build -t express-app . | Builds just like before |
docker run -p 4000:3000 express-app | Runs 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. π