Why your Docker images are too big (and how to fix it)
I've seen production Docker images over 2GB. For a Node.js app. That's 10x larger than it needs to be. Here's how to shrink them.
The Usual Suspect
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]
This image: ~1.1GB. Why?
node:20base image: ~900MB (full Debian + build tools)-
node_moduleswith dev dependencies
-
- Source files,
.git, tests, docs all included
- Source files,
Fix 1: Use Alpine
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
CMD ["node", "server.js"]
Image: ~150MB. node:20-alpine is ~130MB vs ~900MB.
Fix 2: Multi-stage Build
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM node:20-alpine
WORKDIR /app
COPY /app/dist ./dist
COPY /app/package*.json ./
RUN npm ci --production && npm cache clean --force
CMD ["node", "dist/server.js"]
Image: ~100MB. Build tools and dev deps stay in the build stage.
Fix 3: .dockerignore
node_modules
.git
.env*
*.md
tests
coverage
.github
docker-compose*.yml
Without this, COPY . . copies everything — including .git (often 100MB+).
Fix 4: Order Layers for Caching
# Bad: any code change invalidates npm install cache
COPY . .
RUN npm ci
# Good: npm install only reruns when package.json changes
COPY package*.json ./
RUN npm ci --production
COPY . .
Docker caches layers. Put rarely-changing steps first.
Fix 5: Distroless (for Go, Java, Python)
# Go example
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o /server .
FROM gcr.io/distroless/static
COPY /server /server
CMD ["/server"]
Image: ~15MB. Distroless has no shell, no package manager — just your binary.
Quick Wins Summary
| Technique | Typical Savings |
|---|---|
| Alpine base | -70% |
| Multi-stage | -30-50% |
| .dockerignore | -10-30% |
--production flag |
-20-40% |
| Distroless | -90% (compiled langs) |
Checking Image Size
docker images myapp
docker history myapp:latest # See each layer's size
docker scout quickview myapp # CVE scan + size analysis
What's the smallest Docker image you've built? I got a Go API down to 8MB once.
All Rights Reserved