Blog/Quick Tips & Snippets/Docker Multi-Stage Builds for Node.js Apps
POST
May 10, 2025
LAST UPDATEDMay 10, 2025

Docker Multi-Stage Builds for Node.js Apps

Reduce your Node.js Docker image size by 80% using multi-stage builds that separate build dependencies from production runtime artifacts.

Tags

DockerNode.jsDevOpsOptimization
Docker Multi-Stage Builds for Node.js Apps
1 min read

Docker Multi-Stage Builds for Node.js Apps

TL;DR

Use a multi-stage Dockerfile to build in a full Node.js image and run in a slim one, cutting your final image size by 80% or more.

The Problem

A typical Node.js Dockerfile installs all dependencies (including devDependencies), copies the full source, and builds in a single image. The result is a 1GB+ image that ships TypeScript, testing tools, build scripts, and source files that your production app never touches. Larger images mean slower deployments, higher storage costs, and a bigger attack surface.

The Solution

Split your Dockerfile into three stages -- dependencies, build, and production:

dockerfile
# Stage 1: Install dependencies
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
 
# Stage 2: Build the application
FROM node:20-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
 
# Stage 3: Production image
FROM node:20-alpine AS production
WORKDIR /app
ENV NODE_ENV=production
 
# Only copy what's needed to run
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY package.json ./
 
# Remove devDependencies
RUN npm prune --omit=dev
 
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]

Build and check the size difference:

bash
docker build -t myapp .
docker images myapp  # typically 150-200MB vs 1GB+

Why This Works

Each FROM statement starts a fresh filesystem layer. The final production stage only contains the alpine base, production node_modules, and compiled output. All build tools (TypeScript, ESLint, testing frameworks) and source files are left behind in the earlier stages. Docker discards those intermediate stages from the final image entirely, so they add zero bytes to what you ship. The npm prune --omit=dev step ensures even transitive devDependencies are stripped out.

Collaboration

Need help with a project?

Let's Build It

I help startups and established companies design, build, and scale world-class digital products. From deep technical architecture to pixel-perfect UI — let's bring your vision to life.

SH

Article Author

Sadam Hussain

Senior Full Stack Developer

Senior Full Stack Developer with over 7 years of experience building React, Next.js, Node.js, TypeScript, and AI-powered web platforms.

Related Articles

TypeScript Utility Types You Should Know
Feb 10, 20263 min read
TypeScript
Cheatsheet

TypeScript Utility Types You Should Know

Five essential built-in generic utility types in TypeScript that will save you hundreds of lines of code.

Generate Dynamic OG Images in Next.js
Feb 08, 20262 min read
Next.js
OG Images
SEO

Generate Dynamic OG Images in Next.js

Generate dynamic Open Graph images in Next.js using the ImageResponse API with custom fonts, gradients, and data-driven content for social sharing.

GitHub Actions Reusable Workflows: Stop Repeating Yourself
Jan 22, 20263 min read
GitHub Actions
CI/CD
DevOps

GitHub Actions Reusable Workflows: Stop Repeating Yourself

Create reusable GitHub Actions workflows with inputs, secrets, and outputs to eliminate YAML duplication across repositories and teams efficiently.