0

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:20 base image: ~900MB (full Debian + build tools)
    • node_modules with dev dependencies
    • Source files, .git, tests, docs all included

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 --from=builder /app/dist ./dist
COPY --from=builder /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 --from=builder /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

Viblo
Let's register a Viblo Account to get more interesting posts.