TL;DR
Most bloated production images are bloated for three boring reasons: build tools shipped to production, dev dependencies left in, and files copied in that were never needed. Fix those first — base-image choice is only step two.
Use a multi-stage build so compilers, package managers and source code stay in the build stage.
Pick the base image deliberately: slim for compatibility, alpine when your dependencies tolerate musl, distroless when you want no shell at all. Each has real trade-offs — there is no free lunch.
Order layers for cache, add a real .dockerignore , and use BuildKit cache mounts so CI stops re-downloading the internet on every build.
If you run Next.js, turn on standalone output and ship the traced server instead of your whole repository.
Run the image as a non-root user , scan it with Trivy in CI, and sign what you ship.
Measure before and after with docker history and dive .…