The Problem with Fat Images We've all been there: you pull an image, run , and see a 1.2GB monster staring back at you.
That's not just disk space wasted; it's slower pulls, slower deploys, and a larger attack surface.
The usual suspects?
Base images with unnecessary tools, build dependencies left behind, and layers that contain temp files or caches.
I've been guilty of shipping images that could have been 20x smaller.
Over time, I've adopted a few techniques that make a real difference.
Here's what actually works.
Start Small: Choose the Right Base Your base image sets the floor. is around 78MB, but is under 5MB.
If you're running a Go binary, you don't even need a full OS: is literally empty.
For Python, consider instead of (which includes compilers and headers you likely don't need at runtime).
For example, a simple Python app: That's already a fraction of the size of the full image.
Multi-Stage Builds: The Game Changer If you need build tools, don't ship them.
Multi-stage builds let you compile in one stage and copy only the artifacts to a clean final stage.
Here's a Go example: The final image is just the binary.
For a Go app, that's typically 10-20MB.
For a Node.js app, you can do the same: install dependencies in a builder, then copy and your code to a slim runtime image.
Clean Up in the Same Layer Every command creates a layer.
If you install packages and then delete them in a separate , the deletion doesn't remove the data from the previous layer; it just adds a new layer on top.
The size remains.
Always combine cleanup in the same : Also, use for and for to avoid pulling in extras. .dockerignore: Stop Copying Junk If you're copying your entire project directory, you might be including , , test files, or local caches.
A minimal can save megabytes: This also speeds up the build context transfer.
Use Distroless Images If you need a runtime but not a shell, consider Google's distroless images.
They contain only your runtime (e.g., Python, Node) and necessary libraries, no package manager or shell.
This reduces attack surface and size.
For example: Note: you can't into a distroless container (no shell), so debugging is trickier.
That's a trade-off.
Check Your Layers After building, inspect your image with to see what's taking up space: You'll see the size each layer added.
If you see a huge layer for something you thought you cleaned up, you know you missed combining commands.
Also, shows the total size, but gives a breakdown of all your images, containers, and build cache.
Realistic Example: Python API Let's put it together.
A FastAPI app with a couple of dependencies: This avoids installing build dependencies in the final image.
You can even go distroless if you're comfortable.
Final Thoughts Shrinking images isn't just about aesthetics.
Smaller images pull faster, start faster, and have fewer vulnerabilities.
The techniques are straightforward: pick a slim base, use multi-stage builds, clean up in the same layer, and ignore unnecessary files.
Next time you build an image, run and ask yourself: do I need all that?
The answer is usually no.