Why Your Docker Images Are Too Big (And How to Fix It)
Is your Docker image unnecessarily large? Discover common mistakes and practical solutions to optimize your Docker images for production use.
Ever wondered why your Docker images are hundreds of megabytes when your application is just a few kilobytes? Let's fix that.
The Problem
Here's a typical Dockerfile you might see:
# DON'T DO THIS
FROM node:latest
WORKDIR /app
COPY . .
RUN npm install
CMD ["npm", "start"]
This simple Dockerfile can result in a 1GB+ image. Let's see why and how to fix it.
Common Mistakes
1. Using Full Base Images
# Bad: Full Node image
FROM node:latest # ~1GB
# Better: Slim variant
FROM node:slim # ~200MB
# Best: Alpine variant
FROM node:alpine # ~60MB
2. Including Development Dependencies
# Bad: Installing everything
RUN npm install # Includes devDependencies
# Better: Production only
RUN npm ci --only=production
3. Keeping Build Tools
# Bad: Leaving build tools
RUN apt-get update && apt-get install -y build-essential
# Better: Remove after use
RUN apt-get update && \
apt-get install -y build-essential && \
npm rebuild && \
apt-get purge -y build-essential && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/*
Multi-Stage Builds
Basic Example
# Build stage
FROM node:alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM node:alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm ci --only=production
CMD ["npm", "start"]
Real World Example
# Build stage
FROM node:alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Test stage
FROM builder AS tester
RUN npm run test
# Production stage
FROM node:alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm ci --only=production && \
npm cache clean --force
USER node
CMD ["npm", "start"]
Size Comparison
Original Approach:
node:latest + all deps = 1.2GB
Optimized Approach:
node:alpine + prod deps = 150MB
Further Optimized:
multi-stage + alpine = 80MB
Best Practices
1. .dockerignore
node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
README.md
.env
*.test.js
coverage
2. Layer Optimization
# Bad: Many layers
COPY . .
RUN npm install
RUN npm run build
RUN npm prune --production
# Better: Combined layers
COPY . .
RUN npm install && \
npm run build && \
npm prune --production
3. Specific Base Image Tags
# Bad: Using latest
FROM node:latest
# Better: Specific version
FROM node:18.17.1-alpine3.18
Advanced Techniques
1. Custom Base Images
# base.dockerfile
FROM alpine:3.18
RUN apk add --no-cache nodejs npm
# app.dockerfile
FROM custom/node-base
# Your app specific stuff
2. Distroless Images
# Even smaller than Alpine
FROM gcr.io/distroless/nodejs:18
COPY --from=builder /app/dist ./
CMD ["index.js"]
Real World Examples
Next.js Application
# Build
FROM node:alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production
FROM node:alpine
WORKDIR /app
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/public ./public
CMD ["npm", "start"]
React Application
# Build
FROM node:alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production
FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
Monitoring Image Size
Command Line Tools
# Check image size
docker images | grep myapp
# Analyze layers
docker history myapp:latest
# Deep analysis
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive myapp:latest
Conclusion
Remember:
- Use multi-stage builds
- Choose appropriate base images
- Remove unnecessary dependencies
- Optimize layers
- Use .dockerignore
Your Docker images can be both functional and efficient. Start implementing these practices and watch your image sizes shrink!