An oversized container image is rarely a disaster. It is a tax. Every deploy pulls more data, every new node takes longer to become useful, registries store more, and security scanners find more to complain about. The size is usually a side effect of leaving the build toolchain inside the image that actually runs.
Smaller images are not a goal for their own sake. They are a convenient proxy for something better: shipping only what the application needs to run.
Separate Building From Running
The single most useful technique is the multi-stage build. A Dockerfile can define several stages, and only the last one becomes the final image. Earlier stages can hold compilers, package managers and test tools, and you copy just the finished artifact forward.
The shape looks like this in prose: a first stage starts from an image with the full build toolchain, copies in source, installs dependencies and compiles or bundles. A final stage starts from a minimal runtime image and copies only the compiled output from the first stage. The compiler never reaches production.
This works for compiled languages, where the output may be a single binary, and for interpreted ones too, where you can install dependencies in one stage and copy only the installed packages and application code into a lean runtime.
Choosing a Base Image
The base image is a trade-off between size, convenience and debuggability. Rather than naming a winner, here is how the common categories compare.
| Base type | Size | Convenience | Debugging | Watch out for |
|---|---|---|---|---|
| Full distribution image | Large | Everything installed | Easy | Big attack surface, many unused packages |
| Slim variant of a distribution | Smaller | Most basics present | Reasonable | Missing system libraries some packages expect |
| Minimal distribution built for containers | Small | Package manager, small set of tools | Moderate | Differences in the C library can break some binaries |
| Distroless-style runtime | Very small | Only runtime, no shell or package manager | Harder | You cannot exec in and poke around |
| Scratch (empty) | Smallest | Nothing at all | Hardest | Only suits fully static binaries; no CA certificates or timezone data unless you add them |
Choose by asking what the application truly needs at runtime. A service that needs system libraries and a shell for operational scripts is poorly served by an empty image, and a static binary has no business carrying a full operating system. Check the image maintainers’ current documentation for what each variant contains.
Order Your Layers for Caching
Each instruction in a Dockerfile produces a layer, and the build cache reuses layers until something above them changes. A common mistake is copying all source first, then installing dependencies. Every code edit invalidates the dependency install, and builds crawl.
Instead, copy only the dependency manifest, install dependencies, and then copy the rest of the source. Dependencies change rarely, so that expensive layer stays cached across most builds. Small thing, big difference in CI time.
Also combine related shell steps, and clean up in the same step that creates the mess. Deleting a file in a later layer does not shrink the image, because the earlier layer still contains it.
Reduce What Is Inside
Beyond stages and bases:
- Use a build-context ignore file so local junk, version-control directories and secrets never get sent to the builder or copied in by a broad copy instruction.
- Install only runtime dependencies in the final stage. Development packages and test frameworks belong in earlier stages.
- Avoid installing tools “just in case”, like text editors and network utilities. They help attackers as much as you.
- Pin versions of base images and dependencies so builds are reproducible. Floating tags can change underneath you.
- Rebuild regularly. A small image built a year ago still contains year-old vulnerabilities. Rebuilding on a schedule picks up base image fixes.
Security Basics That Cost Almost Nothing
Smaller helps, but these matter more than raw size:
- Run as a non-root user. Add an unprivileged user in the Dockerfile and switch to it. Many clusters can enforce this as policy.
- Never bake secrets into layers. Anything copied or passed as a build argument may be recoverable from the image.
- Scan images in CI and on a schedule, and decide in advance what severity blocks a release.
- Prefer a read-only filesystem at runtime where the app allows it.
- Use trusted base images from sources you can verify, and consider your own curated base so teams are not each choosing differently.
Security requirements differ by organisation and jurisdiction, so treat these as baselines and consult your security team for anything regulated.
A Realistic Example
A team has a web service whose image is large because the single-stage Dockerfile installs the whole build toolchain and leaves the source tree in place. Deploys feel sluggish and the scanner reports a long list of findings, most in packages the app never uses.
They restructure into two stages. The first installs dependencies and builds. The second starts from a slim runtime base, copies the built output, adds a non-root user, and sets the start command. They also reorder the copy instructions so dependency installation caches properly.
The result: smaller image, faster pulls, a much shorter scanner report, and builds that skip the dependency step when only application code changed. They did not need anything exotic, just a more deliberate Dockerfile.
Frequently Asked Questions
Is the smallest possible image always best? No. An image so minimal that you cannot inspect or debug it can slow incident response. Balance size against how your team operates. Debug via ephemeral containers or a separate debug image is a common compromise.
Why did my binary fail on a minimal base image? Frequently a mismatch in the C library or missing shared libraries or CA certificates. Build against the same family as the runtime, or produce a static binary.
Does image size affect Kubernetes performance? It affects how long a node takes to pull the image when starting a pod it has not run before, which matters for scaling and rescheduling. Once running, the size itself has little effect.
Should I use the latest tag? Avoid it for anything you deploy. It makes builds irreproducible and rollbacks ambiguous. Pin to specific versions or digests.
How often should I rebuild images? Regularly, and whenever a relevant security fix lands in your base or dependencies. Automating scheduled rebuilds with scanning keeps this from depending on memory.
Next Step
Take one service and run its current image through your scanner while noting its size. Rewrite the Dockerfile as a multi-stage build with a non-root user, rebuild, and compare. Keep the before and after numbers for your own team so the next conversation about standards starts from evidence.
By SkyStackHub Editorial · Updated November 4, 2026
- container images
- Dockerfile
- multi-stage builds
- container security