Docker Image Optimization: Shrink Your Containers by 90%
Large Docker images waste bandwidth, slow deployments, and increase attack surface. Here's how to dramatically reduce image size.
Why Image Size Matters
A 1.2 GB Node.js image vs a 120 MB optimized one:
Multi-Stage Builds
The single most impactful optimization. Build in one stage, copy only artifacts to a minimal runtime stage.
Before: 1.2 GB
FROM node:20
COPY . .
RUN npm install
RUN npm run build
CMD ["node", "dist/index.js"]
After: 150 MB
FROM node:20 AS builder
COPY . .
RUN npm install && npm run build
FROM node:20-alpine
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]
Use Alpine Base Images
Alpine Linux is ~5 MB vs Ubuntu's ~75 MB. Most language runtimes offer Alpine variants: node:20-alpine, python:3.12-alpine, golang:1.22-alpine.
Layer Optimization
Combine RUN Commands
Every RUN creates a layer. Combine related commands:
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
Order Layers by Change Frequency
Put rarely-changing layers first (dependency install) and frequently-changing layers last (source code copy).
Use .dockerignore
Exclude node_modules, .git, tests, docs, and anything else not needed at runtime.
Language-Specific Tips
Go
Compile to a static binary and use FROM scratch — zero base image overhead. Final image can be under 10 MB.
Python
Use --no-cache-dir with pip. Remove __pycache__ directories. Consider using distroless images.
Node.js
Use npm ci --omit=dev for production. Prune devDependencies in the final stage.
Scan Your Images
Use docker scout or trivy to find vulnerabilities. Smaller images have fewer packages and fewer vulnerabilities.
On TinyPod, optimized images deploy faster and use less of your server's disk space — it's worth the few minutes of Dockerfile tuning.