How We Cut Our Docker Image Size by 80% and Why It Matters
A real walkthrough of shrinking bloated Docker images from 1.2GB to 240MB using multi-stage builds, Alpine, and dependency auditing.
Key takeaways
A real walkthrough of shrinking bloated Docker images from 1.2GB to 240MB using multi-stage builds, Alpine, and dependency auditing.
How We Cut Our Docker Image Size by 80% and Why It Matters#
Our main API image had grown to 1.2GB. Deploys were slow, registry costs were climbing, and cold starts on Kubernetes took 45 seconds. Here's exactly how we fixed it.
The Problem#
A quick docker history revealed the culprits:
- Full Node.js 18 base image (900MB layer)
- Dev dependencies included in the final image
- Leftover build artifacts and cache directories
Step 1: Multi-Stage Builds#
We split the Dockerfile into build and runtime stages:
# Build stage
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Runtime stage
FROM node:18-alpine AS runtime
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
This alone dropped us from 1.2GB to 480MB.
Step 2: Prune Dev Dependencies#
We added a production-only install in the runtime stage:
FROM node:18-alpine AS runtime
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
Down to 310MB.
Step 3: Audit What's Left#
Running npx depcheck found 12 unused packages. We removed them and dropped another 70MB.
Results#
| Metric | Before | After |
|---|---|---|
| Image size | 1.2GB | 240MB |
| Pull time | 38s | 8s |
| Cold start | 45s | 12s |
| Registry cost | $180/mo | $40/mo |
Best Practices#
- Always use multi-stage builds for compiled or bundled applications
- Use Alpine or distroless base images in production
- Run
npm ci --omit=devto exclude dev dependencies - Add a .dockerignore to prevent copying node_modules, .git, and test files
- Audit images quarterly with
docker scoutortrivy
Small images mean faster deploys, cheaper storage, and a smaller attack surface. There's no reason to ship your test framework to production.
Get the DevOps Troubleshooting Cheat Sheet
Subscribe and get our free one-page reference for the errors that eat an afternoon — CrashLoopBackOff, OOMKilled, Terraform state locks, and more — plus new guides as we publish them.
Model Fallback Policies for Customer-Facing AI: The Routing Rules That Kept SLA Intact
A real-world model fallback guide for customer-facing AI systems, covering how one team preserved response quality and support SLAs during a partial provider degradation.
AWS Cost Audit: 7 Things We Found Wasting Money Every Month
A real cost audit uncovered idle load balancers, oversized RDS instances, and forgotten snapshots. Here's what we found and how we fixed each one.
More from DevOps
Explore more articles in this category
Zed Delta and the Pull Request: Obsolete, or Just Overloaded?
Zed says agents made pull requests obsolete and shipped Delta to replace them. The review unit needs rethinking, but review itself, and the gates around it, stay.
Kubernetes Readiness Probes Lie During Rolling Updates
A green readiness probe means the probe endpoint answered, nothing more. The gaps that cause 502s during rollouts, and the Deployment settings that close them.
KYAML: Kubernetes YAML Without the Norway Problem
Kubernetes is promoting KYAML, a strict subset of YAML that any parser accepts. It kills type coercion and indentation bugs without a migration.
You might have missed
Evergreen posts worth revisiting.