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:

  1. Use multi-stage builds
  2. Choose appropriate base images
  3. Remove unnecessary dependencies
  4. Optimize layers
  5. Use .dockerignore

Your Docker images can be both functional and efficient. Start implementing these practices and watch your image sizes shrink!