Large Docker images slow down CI/CD pipelines, increase registry storage costs, delay deployments, and extend Kubernetes pod startup times. While Docker makes containerizing applications straightforward, many production images contain unnecessary SDKs, package caches, debugging tools, and unused dependencies that significantly increase image size.
Rather than blindly applying optimization tips, this article explains eight techniques that consistently reduce Docker image size, why they work, and how to measure their impact in your own environment.
Note: Image size improvements vary depending on your application, .NET version, Linux distribution, and installed dependencies. Always benchmark before and after each optimization.
Why Smaller Images Matter
A 1 GB image isn't just a storage problem.
Large images result in:
Slower CI/CD pipelines
Longer deployments
Increased bandwidth usage
Slower Kubernetes pod startup
Higher registry storage costs
Longer rollback times
In production environments with dozens of deployments per day, even a modest reduction in image size can improve operational efficiency.
Measuring Before Optimizing
Never optimize based on assumptions.
Useful commands include:
docker images
docker history your-image
docker image inspect your-image
These commands help identify which layers consume the most space.
Technique #1 – Use Multi-Stage Builds
One of the largest mistakes is shipping the .NET SDK with your application.
Poor Dockerfile:
FROM mcr.microsoft.com/dotnet/sdk:9.0
WORKDIR /app
COPY . .
RUN dotnet publish -c Release
ENTRYPOINT ["dotnet","MyApi.dll"]
The SDK includes compilers, build tools, NuGet packages, and other components that aren't required at runtime.
A multi-stage build separates compilation from execution.
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /publish
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
COPY --from=build /publish .
ENTRYPOINT ["dotnet","MyApi.dll"]
This is often the single most effective optimization for .NET applications.
Technique #2 – Choose the Right Base Image
Not every application requires the same runtime image.
Typical options include:
| Base Image | Best For |
|---|---|
| SDK | Build environments only |
| ASP.NET Runtime | Web APIs and MVC applications |
| Runtime | Console applications |
| Chiseled/Distroless | Production containers with minimal OS |
Smaller runtime images reduce attack surface while decreasing download time.
Always choose the smallest image that supports your application requirements.
Technique #3 – Reduce Build Context
Many repositories send unnecessary files into the Docker build context.
Typical offenders include:
bin/obj/.git/node_modules/Test results
IDE settings
Create a .dockerignore file.
bin/
obj/
.git/
.vscode/
node_modules/
Smaller build contexts reduce build time and prevent unnecessary cache invalidation.
Technique #4 – Combine RUN Instructions
Every RUN instruction creates a new image layer.
Instead of:
RUN apt update
RUN apt install curl
RUN rm -rf /var/lib/apt/lists/*
Use:
RUN apt update && \
apt install -y curl && \
rm -rf /var/lib/apt/lists/*
Combining related operations minimizes image layers and prevents temporary files from remaining in intermediate layers.
Technique #5 – Remove Package Cache
Package managers leave behind downloaded metadata.
Example:
RUN apt update && \
apt install -y tzdata && \
rm -rf /var/lib/apt/lists/*
Without cleanup, package indexes remain inside the image and increase its size unnecessarily.
The same principle applies to npm, pip, apk, and other package managers.
Technique #6 – Publish with Trimming
Modern .NET supports assembly trimming.
dotnet publish \
-c Release \
-p:PublishTrimmed=true
Trimming removes unused framework assemblies.
Benefits include:
Smaller deployment size
Faster image downloads
Reduced disk usage
However, applications using heavy reflection or dynamic loading should be tested carefully because trimming can remove assemblies required at runtime.
Technique #7 – Enable ReadyToRun Only When Appropriate
ReadyToRun (R2R) precompiles assemblies.
dotnet publish \
-p:PublishReadyToRun=true
Trade-offs:
Join the conversation! Your thoughts help the community grow.