๐ŸŒ Detecting your locationโ€ฆ
๐Ÿ“ข Advertisement โ€” Configure AdSense in Appearance โ†’ Customize โ†’ AdSense Settings

How to Optimize Docker Images: Reduce Size by 90% in 2026

โฑ๏ธ5 min read  ยท  927 words

Bloated Docker images slow deployments, waste storage, increase attack surface, and cost money in bandwidth. A typical unoptimized Node.js or Python image can be 1GB+; with the right techniques, the same app fits in under 100MB. Here’s how to shrink Docker images dramatically.

Why Image Size Matters

  • Faster deployments: Smaller images pull and start faster, especially when scaling
  • Lower costs: Less registry storage and bandwidth (matters at scale)
  • Smaller attack surface: Fewer packages means fewer vulnerabilities
  • Faster CI/CD: Building and pushing smaller images speeds up pipelines

Technique 1: Multi-Stage Builds (Biggest Win)

Build in one stage with all the build tools, then copy only the final artifact into a clean, minimal image:

# ๐Ÿ› Single-stage โ€” includes build tools, dev deps, source (1.1GB)
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
CMD ["node", "dist/index.js"]

# โœ… Multi-stage โ€” final image has only runtime + built output (150MB)
# Build stage
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Runtime stage โ€” clean slate
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev            # production deps only
COPY --from=builder /app/dist ./dist   # copy ONLY the built output
CMD ["node", "dist/index.js"]

Technique 2: Use Minimal Base Images

Base Image Approx Size Notes
node:20 ~1.1 GB Full Debian โ€” avoid for production
node:20-slim ~240 MB Trimmed Debian โ€” good default
node:20-alpine ~180 MB Alpine Linux โ€” small, common
gcr.io/distroless/nodejs ~170 MB No shell/package manager โ€” most secure
# Alpine โ€” smallest common choice
FROM node:20-alpine

# Distroless โ€” no shell, no package manager, minimal attack surface
FROM gcr.io/distroless/nodejs20-debian12
COPY --from=builder /app/dist /app/dist
CMD ["/app/dist/index.js"]

Technique 3: Optimize Layer Caching

# ๐Ÿ› Copying everything first breaks caching โ€” any code change
#    re-runs npm install
COPY . .
RUN npm ci

# โœ… Copy dependency files first, install, THEN copy source
COPY package*.json ./
RUN npm ci                # this layer is cached unless package.json changes
COPY . .                  # only this layer rebuilds on code changes
RUN npm run build

Order Dockerfile instructions from least-frequently-changed to most. Dependencies change rarely, source changes constantly โ€” so install dependencies before copying source to maximize cache hits.

Technique 4: Use .dockerignore

# .dockerignore โ€” exclude files that bloat the build context
node_modules
npm-debug.log
.git
.gitignore
.env
.env.*
dist
coverage
.vscode
.idea
README.md
*.md
.DS_Store
tests
**/*.test.js

Without .dockerignore, COPY . . sends your entire directory (including node_modules, .git) to the build โ€” slowing builds and potentially leaking secrets.

Technique 5: Combine and Clean Up RUN Commands

# ๐Ÿ› Each RUN creates a layer; cleanup in a separate layer doesn't shrink
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*   # too late โ€” previous layers keep the data

# โœ… Combine into one layer with cleanup in the same RUN
RUN apt-get update && \
    apt-get install -y --no-install-recommends curl && \
    rm -rf /var/lib/apt/lists/*   # cleanup in the SAME layer

Python Example: Multi-Stage + Slim

# Build stage โ€” compile dependencies
FROM python:3.12 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

# Runtime stage โ€” slim base + only installed packages
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]
# Result: ~150MB instead of ~1GB

Measuring and Analyzing Image Size

# Check image size
docker images myapp

# Analyze layers to find what's taking space
docker history myapp:latest

# Use 'dive' for detailed layer analysis
dive myapp:latest
# Shows wasted space and what each layer adds

Optimization Checklist

  • Use multi-stage builds โ€” copy only runtime artifacts to the final image
  • Use alpine, slim, or distroless base images
  • Copy dependency files and install before copying source (layer caching)
  • Add a comprehensive .dockerignore
  • Install production dependencies only (npm ci --omit=dev, pip --no-cache-dir)
  • Combine RUN commands and clean up in the same layer
  • Pin base image versions for reproducibility

Frequently Asked Questions

Q: Is Alpine always the best base image?
A: Usually smallest, but Alpine uses musl libc which occasionally causes issues with native modules or DNS. If you hit compatibility problems, use slim (Debian-based) โ€” it’s still small and more compatible. Distroless is best for security.

Q: What is distroless and should I use it?
A: Distroless images contain only your app and its runtime โ€” no shell, no package manager, minimal OS. This shrinks size and dramatically reduces attack surface. Great for production, but harder to debug (no shell to exec into).

Q: How much smaller can I realistically get?
A: Multi-stage builds plus a minimal base commonly reduce images by 70-90%. A 1GB Node.js image often becomes 100-150MB. Distroless can go smaller still.

Q: Does image size affect runtime performance?
A: Not directly at runtime, but smaller images pull and start faster (important for autoscaling and cold starts), use less registry storage, and have fewer vulnerabilities. The benefits are in deployment speed and security.

Q: Why does my image stay large despite cleanup commands?
A: Docker layers are immutable โ€” if you add files in one layer and delete them in another, the deleted data still exists in the earlier layer. Clean up in the SAME RUN command, or use multi-stage builds to leave the bloat behind entirely.

Conclusion

Optimizing Docker images comes down to a few high-impact techniques: multi-stage builds (copy only what runtime needs), minimal base images (alpine, slim, or distroless), smart layer caching (dependencies before source), and a thorough .dockerignore. Together these commonly shrink images by 70-90% โ€” from 1GB+ down to 100-150MB. Smaller images deploy faster, cost less, and expose a smaller attack surface. Apply the checklist to your Dockerfiles and measure the results with docker history or dive.

โœ๏ธ Leave a Comment

Your email address will not be published. Required fields are marked *

๐ŸŒ Read in:๐Ÿ‡ฌ๐Ÿ‡ง English๐Ÿ‡ฉ๐Ÿ‡ช Deutsch๐Ÿ‡ง๐Ÿ‡ท Portuguรชs๐Ÿ‡ธ๐Ÿ‡ฆ ุงู„ุนุฑุจูŠุฉ๐Ÿ‡ฎ๐Ÿ‡ณ เคนเคฟเคจเฅเคฆเฅ€๐Ÿ‡ง๐Ÿ‡ฉ เฆฌเฆพเฆ‚เฆฒเฆพ