$ docker images | grep ‘before’
myapp:before 1.2GB "This is fine" 🔥 myapp:after 50MB "Wait, what?" 🤯
How we reduced our Docker image by 96% and cut deployment time from 10 minutes to 30 seconds.
The Problem: Build Artifacts in Production
❌ The Bad Old Way
FROM node:18 WORKDIR /app # Copy everything (including .git, node_modules, tests) COPY . . # Install ALL dependencies (including dev dependencies) RUN npm install # Build RUN npm run build # Now we have: # - node_modules (500MB) # - Source code (100MB) # - Git history (50MB) # - Tests (30MB) # - Build tools (200MB) # Total: 1.2GB 😱
✅ Multi-Stage Build
# Stage 1: Build FROM node:18 AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . RUN npm run build # Stage 2: Production FROM node:18-alpine # Alpine = tiny base image WORKDIR /app # Only copy what we need! COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules COPY package.json ./ USER node # Security: don't run as root! CMD ["node", "dist/server.js"] # Final image: 50MB 🎉 # - No source code # - No dev dependencies # - No build tools # - No Git history
📊 Real-World Impact
| Metric | Before | After | Improvement |
|---|---|---|---|
| Image Size | 1.2 GB | 50 MB | 96% smaller |
| Pull Time | 5 min | 10 sec | 30x faster |
| Deploy Time | 10 min | 30 sec | 20x faster |
| Registry Costs | $200/month | $10/month | 95% cheaper |
| Attack Surface | High | Minimal | Much safer |
50MB
Final Image Size
30s
Deploy Time
96%
Size Reduction
💡 Pro Tips
- Use Alpine base images – 5MB vs 150MB for standard images
- Order matters – Put rarely-changing stuff first (package.json before source code)
- Use .dockerignore – Like .gitignore but for Docker (exclude tests, docs, .git)
- Don’t run as root – Add USER node before CMD
- Multi-stage = free – No performance penalty, only benefits
“Our CI/CD went from timing out to completing in under a minute. Our AWS bill dropped by $150/month just from smaller images. Multi-stage builds paid for themselves in week one.”
