How to Shrink Docker Images: Six Practical Optimization Methods

Docker makes packaging and deploying applications much easier — but if you aren’t careful, image sizes can balloon over time. Big images cost you more in storage, longer in deployment and CI/CD pipelines, worse in network time, and present larger attack surfaces. So optimizing images is not just “nice to have” — it has technical, economic, and security impacts.

Below are six proven ways to reduce Docker image size, along with technical trade-offs to consider. You can apply them in your projects, CI/CD pipelines, or during architecture reviews.


Why Image Size Matters

  • Performance & Speed: Smaller images pull & startup faster. For large-scale systems (e.g. Kubernetes), minutes saved per deploy add up.
  • Cost: Less bandwidth, less storage usage (both on registries and on nodes).
  • Security & Maintenance: Fewer libraries = fewer vulnerabilities. Smaller attack surface. Easier to scan and audit.
  • Reliability: Less chance of failures in transit or during disk exhaustion.

Methods to Reduce Docker Image Size

Here are six methods you can implement now. I also include which ones are more suitable depending on your stack and constraints.

Method What to Do Technical Implementation Pros / Trade-offs
1. Use minimal / distroless base images Choose base images with tiny footprints (Alpine, BusyBox, or distroless images). e.g. FROM alpine, or gcr.io/distroless/nodejs etc. Remove unnecessary OS utilities. Pros: smallest image, lower attack surface. Cons: harder debugging (no shell), might need more build effort to include needed dependencies. Also some minimal images have limited support or compatibility issues.
2. Multistage builds Separate build & runtime stages. Build dependencies in one stage, copy only what’s needed to runtime image. In Dockerfile: FROM builder-base AS build → install, compile → COPY --from=build to lean base → final image contains only runtime code. Pros: reduces leftover build tools, dev libs, caches. Trade-offs: more complex Dockerfiles; build time may increase slightly; build infrastructure must support multistage.
3. Minimize number of layers Combine related commands (especially RUN) and avoid many small layers. Use --no-install-recommends / similar flags. Instead of separate RUN apt-get install A then RUN install B etc, combine: RUN apt-get update && apt-get install … && clean …. Clean temp caches in same layer. Pros: smaller image, faster builds. Trade-offs: errors harder to debug; less modular build; more verbose single commands.
4. Optimize Dockerfile order & caching Place dependency installation, OS updates, etc. before copying application code; keep code changes lower in layers. That way Docker’s layer cache helps avoid rebuilding unchanged layers. For example, move COPY . . after RUN apt-get … and ‘npm install’ etc. Pros: faster CI builds; less repeated work. Trade-offs: you must maintain discipline; small code changes still invalidate later layers; caching sometimes leads to stale content if not managed well.
5. Use .dockerignore (ignore unnecessary files) Exclude local files/folders not needed in the image (docs, tests, local configs, git dirs etc). Create .dockerignore alongside Dockerfile. Example entries: .git, node_modules (if built inside), logs, temp files. Pros: smaller build context, faster builds, less chance of leakage of sensitive files. Trade-offs: you might accidentally ignore needed files; need good review.
6. Keep application data outside of the image Don’t bake runtime data/config/database/file uploads into the image. Use volumes, external storage. Use Docker volumes or bind mounts; with Kubernetes use PVC (Persistent Volume Claims); keep configs or env vars external (e.g. secrets, mounted config files). Pros: images remain immutable, stateless; easier portability; smaller base size. Trade-offs: more moving parts; require management of state & backups; mounting volumes adds complexity.

Additional Tools & Practices

To make these methods more manageable and to enforce them across your teams:

  • Dive — explore what’s inside your image, which layers are big, where inefficiencies lie.
  • Docker-Slim / SlimToolKit — tools that help you strip out unnecessary bits. Sometimes huge reductions (orders of magnitude) are possible.
  • Squashing layers — merging layers to reduce overhead. Use with caution (trade-off rebuild cache).
  • Vulnerability Scanning (e.g. Trivy) — doesn’t reduce size, but ensures that what remains is secure.

My Take & Suggested Workflow

Here’s a workflow I recommend, based on experience:

  1. Audit current images — identify biggest images, which layers or dependencies contribute most size.
  2. Baseline vs target — set size goals (e.g. “under 200 MB for this service”, or reduce by 50 % from current).
  3. Apply minimal base + multistage first — biggest wins often here.
  4. Use .dockerignore and reorder Dockerfile next.
  5. Automate checks in CI — e.g. ensure builds fail if image > threshold, run scanning.
  6. Monitor after deployment — check pull times, storage usage. Adjust if performance regressions.

Example .Dockerfile multistage build

# Stage 1: Build
FROM node:20-alpine AS build

WORKDIR /app

# Install only dependencies first for better caching
COPY package*.json ./
RUN npm ci --only=production

# Copy source code
COPY . .

# Optional: build step (for React/Angular/TypeScript)
# RUN npm run build

# Stage 2: Runtime (slim image)
FROM node:20-alpine AS runtime

WORKDIR /app

# Copy node_modules & code from build stage
COPY --from=build /app /app

EXPOSE 3000

CMD ["node", "server.js"]

.dockerignore example

# Ignore VCS
.git
.gitignore

# Node.js
node_modules
npm-debug.log
yarn-error.log

# Python
__pycache__/
*.pyc
*.pyo
*.pyd
*.pytest_cache
.venv
.env

# Docker
Dockerfile
docker-compose*.yml
.dockerignore

# IDE / Editor
.vscode
.idea
*.swp

Conclusion

Reducing Docker image size isn’t just about bragging rights — it has concrete benefits: faster builds, less bandwidth, lower costs, reduced risk. While some techniques add complexity, the gains in performance & security often justify the effort.

If you begin implementing a few of these in your next project, you’ll likely see 20-80% size reductions, depending on how unoptimized you are now.

Related: Docker Build Cache Optimization: Cutting CI/CD Pipeline Latency by 80%.

Related: Why I Ditched Docker Compose and Went Bare-Metal for My ERP Staging.


Discover more from Susiloharjo

Subscribe to get the latest posts sent to your email.

Discover more from Susiloharjo

Subscribe now to keep reading and get access to the full archive.

Continue reading