Skip to main content
Cloud DevOps Solutions
Get a quote Talk to an engineer

Docker & Containerization

How to Reduce Docker Image Size Without Breaking the Application

We reduce Docker image size by measuring every layer, removing build tooling with multi-stage builds, switching to a smaller base image and stripping unneeded files. Results vary by language and workload, and every change is checked against your tests so behaviour does not change.

Key takeaways

  • Measure first: most oversized images are one or two layers, not a general problem.
  • The biggest wins usually come from removing build tools and choosing a smaller runtime base.
  • Savings vary widely by language and dependencies, so we report your actual before and after numbers.
  • Smaller images pull faster, which speeds deploys, autoscaling and CI.
  • We stop shrinking when the next saving would hurt operability or compatibility.

Why size matters more than it looks

An oversized image is rarely the thing that pages you, which is why it stays oversized. But the cost shows up everywhere. A new Kubernetes node cannot start your pod until it has pulled the image. A CI job spends its first minute downloading layers. A rollback is slower than it should be. And every extra package in the image is something that a vulnerability scanner will eventually flag.

This is one part of our Docker containerisation work. Reduction is not a vanity exercise: we do it where the saving shows up in deploy time, cost or risk, and we stop when it does not.

Start by measuring, not guessing

Before changing anything, we look at where the bytes are.

# size and layer breakdown
docker image ls myapp:current
docker history --no-trunc --format '{{.Size}}\t{{.CreatedBy}}' myapp:current | head -20

# interactive layer explorer
dive myapp:current

Nearly every case has the same few culprits:

  • A full toolchain (compiler, build-essential, headers) left in the final image.
  • node_modules or virtual environments containing dev dependencies.
  • Package manager caches such as apt lists or pip caches never cleaned in the same RUN layer.
  • A COPY . . that pulled in .git, test fixtures, local databases and build output. See .dockerignore and layer caching.
  • A base image far larger than the application needs.

The techniques, in order of payoff

1. Multi-stage builds

Move compilation and dependency installation into a builder stage and copy only the output into the runtime stage. This one change usually accounts for most of the saving. The mechanics are covered in Dockerfile multi-stage builds.

2. A smaller runtime base

Starting pointTypical moveCaveat
Full language imageSlim variant of the same distributionCheck native libraries still load
Slim imageDistroless runtimePlan how you will debug without a shell
Debian or UbuntuAlpineTest for musl compatibility
Static binaryscratch or distroless staticCopy CA certificates and timezone data

3. Clean up inside the same layer

A file deleted in a later layer still exists in the earlier one. Cleanup must happen in the layer that created the mess:

RUN apt-get update \
 && apt-get install -y --no-install-recommends ca-certificates curl \
 && rm -rf /var/lib/apt/lists/*

4. Strip what the application never reads

Documentation, man pages, source maps, test directories inside dependencies, and unused locales add up. For Java, jlink can build a trimmed runtime containing only the modules needed. For Python, avoid installing build dependencies in the final stage and use --no-cache-dir.

5. Install only production dependencies

npm ci --omit=dev, pip install from a compiled wheel set, or go build with -ldflags="-s -w" all reduce what ships. These are small, safe changes.

A worked example, by language

The same goal looks different in each ecosystem, which is why we measure rather than apply a recipe.

LanguageUsual cause of bloatTypical fix
Node.jsDev dependencies and build output in the final imageBuild in one stage, install production dependencies only in the runtime stage
PythonCompilers installed to build wheels, pip cachesBuild wheels in a builder stage and copy the virtual environment across
JavaFull JDK and build tool in the runtime imageRun on a JRE or trimmed runtime, ship only the packaged jar
Go and RustLittle, if built as static binariesCheck for debug symbols and an oversized base

For compiled languages that already produce a single binary, the honest answer is often “you are nearly there”. We would rather tell you that than invent work.

Compressed size versus on-disk size

Two numbers get confused. Registries and pulls deal with compressed layers, while docker image ls shows the uncompressed size on disk. Pull time follows the compressed figure and the number of layers that are not already cached on the node, not the headline size. This matters for ordering: layers that rarely change, such as the base and dependencies, should come first so nodes already hold them and only the small application layer is fetched on each deploy. Smaller helps, but stable layers help as much.

Guarding against regrowth

Images grow back quietly. A new dependency, a copied directory or a forgotten debug tool adds weight one pull request at a time. A simple CI step compares the built image size to a recorded budget and fails or warns when it is exceeded:

size=$(docker image inspect myapp:ci --format '{{.Size}}')
echo "image size: ${size} bytes"

We then compare that value to a threshold stored in the repository. The budget is set from the measured baseline plus agreed headroom, and a human can raise it deliberately with a reason in the pull request.

Where we stop

There is a point where further shrinking costs more than it saves. Removing the shell from an image that your on-call team debugs weekly is a bad trade. Chasing the last few megabytes by hand-picking shared libraries creates an image nobody wants to maintain. We usually stop when three conditions hold: the image contains only what the application needs, it is built from a base your team can operate, and further reductions would require unusual tricks.

A note on headline numbers. Our service description mentions typical reductions of 60 to 80 percent. That figure describes images that started with full toolchains and large bases. It is not a promise. A lean image has less to gain, and we say so after measuring.

Failure modes after shrinking

  • TLS errors because CA certificates were not carried into a minimal base.
  • Wrong time zones or locale behaviour when tzdata is missing.
  • Crashes on startup from a missing shared library that the old image had by accident.
  • Healthchecks that call curl or wget which no longer exist. We switch to a built-in check or an application endpoint tested by the orchestrator.
  • Slower debugging. We provide a debug variant or document kubectl debug with an ephemeral container.

Delivery and keeping it small

We deliver pull requests with a comparison table per service and keep behavioural checks in CI. To prevent regression, a CI step can compare the new image size against the previous release and warn when it grows past an agreed threshold. Because a smaller base has fewer packages, it also improves results in vulnerability scanning with Trivy.

How to tell it is working

Compare image size, pull time on a cold node, and the number of scanner findings before and after. A healthy outcome is faster scale-out, shorter CI downloads, a shorter findings list and an application that behaves exactly as before. If you want your images measured, get in touch.

References

How we deliver it

  1. 01

    Baseline the images

    We record compressed and uncompressed size, layer breakdown and pull time for each service.

  2. 02

    Find the heavy layers

    Using docker history and dive, we locate caches, build tools, test data and duplicated copies.

  3. 03

    Remove what is not needed at runtime

    Compilers, dev dependencies, documentation, source maps and package caches stay out of the final stage.

  4. 04

    Right-size the base

    We evaluate slim, Alpine and distroless bases against your native dependencies and debugging needs.

  5. 05

    Verify behaviour

    The slimmer image must pass your tests, start cleanly and handle TLS, timezones and signals as before.

  6. 06

    Report and lock in

    You receive a before and after table, plus a CI size check so images do not grow back unnoticed.

Tools and technologies

DockerBuildKitdivedocker historydocker-slimdistrolessAlpine LinuxDebian slimTrivyGitHub Actions

Frequently asked questions

How much smaller can a Docker image get?
It depends entirely on what is in it. Images that ship compilers, dev dependencies and a full operating system often shrink dramatically, while an already lean Go or Rust service has little left to remove. We never quote a figure in advance. We measure your images and report the real before and after.
Is image size really important?
It matters most where images are pulled often: autoscaling nodes, ephemeral CI runners, serverless-style workloads and multi-region deploys. Smaller images also contain fewer packages, so fewer vulnerabilities. For a service deployed twice a year on one server, size is a low priority.
Is Alpine always the best way to shrink an image?
No. Alpine uses musl libc, which can cause subtle issues with native Node modules, Python wheels and some JVM tooling. Debian slim or distroless often gives most of the saving with fewer surprises. We test candidates against your actual dependencies rather than assuming.
Will shrinking images break our application?
It can if done carelessly, which is why we run your tests and a startup check against every change. Typical breakages are missing CA certificates, timezone data, shared libraries or a shell that scripts expect. We find these before production and document any trade-offs.
Should we use docker-slim or similar tools that remove files automatically?
We treat them as an analysis aid, not a build step. Automated trimming can remove files used only on rare code paths, causing failures that appear weeks later. A deliberate multi-stage build with explicit contents is more predictable and easier to review.

Talk to an engineer about reduce docker image size

Book a free 30-minute call. We review your current setup and tell you honestly whether and how we can help.

Written by Saddique Khan, DevOps / SRE / Cloud Consultant Engineer. Last updated 2026-10-08.