Why our images grew past a gigabyte, how we found what nothing used, and why smaller images made deploys, pods and CI faster.
Every container image is a cost you pay again and again: on every deploy, on every new pod, on every server that has to pull it. Ours had quietly grown past a gigabyte each. When one of our servers ran out of disk, we went looking for why.
Images grow by accident
Services don’t get heavy all at once. A feature needs a library, the feature goes away, the library stays. A one-off job pulls in a big framework and nobody takes it out. Over time, what’s installed drifts away from what the code actually uses.
Nothing breaks, so nobody notices. The service still starts, the tests still pass. It just carries more every time it ships, and older services carry the most.
Start from what runs, not from what’s installed
The useful question isn’t “how do I compress this image?” It’s “what in here does the running service actually need?”
So we went through the old and legacy code first, not the Dockerfile:
- Find the code nothing calls. Endpoints and jobs with no traffic are dead weight. Delete them.
- Follow each dependency back to its users. A library stays installed as long as one import of it survives somewhere. Once the dead code is gone, many libraries have no users left, and they can go too.
- Go after the heaviest first. A handful of large libraries, like machine-learning frameworks and browser automation, usually make up most of the size. Removing one of them is worth more than any amount of tuning.
- Rebuild and measure. Check the size after each pass, and keep going while it keeps paying.
That clean-up did most of the work. Our runtime images came out about 90% smaller, and a five-app deploy now pulls about a tenth of what it used to.
Why it paid off far beyond disk space
Smaller images helped in places that had nothing to do with storage:
- Pods start sooner. A server has to pull the image before a pod can start. Less to pull means faster deploys and faster scale-ups when traffic rises.
- Servers stay healthy. Images share a server’s disk with everything else. When that disk fills up, Kubernetes starts clearing old images and then moving pods off the server.
- Less to reserve. A service that no longer loads a heavy framework needs less memory and CPU set aside for it. The same clean-up let us cut what the service reserves by well over half.
- Faster CI. Fewer dependencies means less to install, cache and push on every build. How we cut CI minutes by 71% is the other half of this story.
Then make what’s left smaller
Once the dead weight is gone, the usual techniques tidy up the rest:
- Build in one stage, ship from another. Compile with all the build tools, then copy only the finished result into a clean final image.
- Start from a slim base. Most official images have a slim variant without compilers and extras.
- Clean up in the same step that made the mess. Deleting files in a later step hides them but doesn’t remove them from the image.
- Keep junk out of the build. A
.dockerignorefile stops tests, git history and local files from sneaking in. - Use CPU-only builds of ML libraries if the container never touches a GPU.
- Share a base image across apps, so each server stores the common part once.
These help, but they work on what’s left. They can’t fix what shouldn’t be there.
The lesson
Delete before you optimise. No layer trick beats not shipping the thing at all.
This was one part of a wider effort to stop paying for what we don’t use. The server costs case study has the rest, and Cloud cost is a product problem is why it matters this early.
Quick answers
- Why do Docker images get so big?
- Mostly because of what they carry, not how they're built. Libraries added for features that no longer exist, and heavy frameworks pulled in for one job, stay in the image long after anything uses them.
- How do I make a Docker image smaller?
- Remove what the app doesn't use first: dead code, unused dependencies, leftover tools. Then build in stages, start from a slim base and keep build-time files out of the final image.
- Why does Docker image size matter on Kubernetes?
- Every server has to pull an image before a pod can start on it. Big images slow every deploy and scale-up, and fill the server's disk.
- What is a multi-stage Docker build?
- A Dockerfile that builds in one stage and copies only the finished result into a clean final stage, so compilers and build files never reach the image you ship.