Optimizing Docker Images: A Guide to Multi-Stage Builds
Streamline your CI/CD pipeline by mastering multi-stage builds to create leaner, faster, and more secure container images.

In the early days of containerization, developers often struggled with 'image bloat.' Including build-time dependencies, compilers, and source code in a production image resulted in mult-gigabyte files that were slow to push, slow to pull, and riddled with unnecessary security vulnerabilities. This inefficiency bogged down CI/CD pipelines and increased cloud storage costs significantly.
Multi-stage builds changed the game by allowing engineers to use multiple 'FROM' statements in a single Dockerfile. By separating the environment used to compile the application from the environment used to run it, you can ensure that only the essential binary and its runtime dependencies make it into the final artifact. This tutorial explores how to implement this pattern to achieve production-ready efficiency.
The Problem with Single-Stage Dockerfiles
When you write a standard Dockerfile, every instruction adds a new layer to the image. Even if you delete a file in a subsequent instruction, that file still exists in the underlying layers. For many compiled languages like Go, Rust, or even TypeScript (which requires a heavy Node.js build environment), the tools needed to build the app are far larger than the app itself.
A typical Go application might require a 500MB SDK to compile, but the resulting binary is only 20MB. If you ship the entire SDK, you are wasting 480MB of space. This leads to longer deployment times, as the container registry and the production host must transfer nearly half a gigabyte of redundant data every time you scale or update your service.
Understanding Multi-Stage Build Syntax
The core concept of a multi-stage build is naming your stages using the 'AS' keyword. You can have a 'build' stage where you perform heavy lifting and a 'final' stage where you only copy over the necessary artifacts. This process effectively 'squashes' the history, leaving behind the heavy build layers and keeping only what is required for execution.
# Stage 1: The Builder
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o main .
# Stage 2: The Final Runtime
FROM alpine:latest
WORKDIR /root/
COPY --from=builder /app/main .
CMD ["./main"]In the example above, the final image is based on 'alpine', which is roughly 5MB. We only copy one file—the 'main' binary—from the first stage. The Go compiler, cache, and source code from the 'builder' stage are all discarded entirely after the build completes.
Key Benefits for Your DevOps Workflow
Adopting this approach isn't just about saving disk space; it directly impacts the reliability and security of your cloud infrastructure. Here are the primary advantages:
- Reduced Attack Surface: By removing compilers, shell utilities, and package managers (like npm or pip) from the production image, you give attackers fewer tools to use if they gain unauthorized access.
- Faster CI/CD Cycles: Smaller images mean faster 'docker push' and 'docker pull' operations. This can shave minutes off your deployment pipeline.
- Lower Egress Costs: Cloud providers charge for data transfer between regions. Reducing image size reduces these billable costs.
- Simplified Maintenance: You don't need to maintain separate Dockerfiles for development and production.
Advanced Techniques: Distroless and Scratch
If you want to take optimization to the absolute limit, look beyond Alpine Linux to 'Scratch' or 'Distroless' images. A 'scratch' image is an empty starting point with zero files. This is perfect for statically linked binaries that carry all their dependencies internally.
The most secure container is the one that contains absolutely nothing except your application binary and its required runtime environment.
Google's Distroless project provides images that contain only your application and its runtime dependencies. They do not contain package managers, shells, or any other programs you would expect to find in a standard Linux distribution. This further hardens your production environment against common exploits.
Best Practices for Layer Caching
To maximize the speed of your builds, order matters. Docker caches each layer based on the instructions. If a layer changes, every subsequent layer must be rebuilt. You should copy your dependency manifests (like package.json or go.mod) and run your install command before copying the rest of your source code.
- Use a specific base image version instead of 'latest' to ensure reproducibility.
- Keep the 'final' stage as clean as possible by using .dockerignore to exclude local environment files.
- Leverage BuildKit features like mount caches to speed up package installations across different builds.
- Regularly audit your images using tools like 'dive' to inspect layer contents.
Implementing for Single-Page Applications (SPA)
Multi-stage builds are also perfect for React or Vue applications. Stage one installs Node.js and builds the static assets using 'npm run build'. Stage two simply uses an Nginx or Apache image to serve those static files. This avoids shipping the massive 'node_modules' folder to production, which is a common mistake for junior DevOps engineers.
Expert insights
- Always use the .dockerignore file to exclude the .git directory and local build logs; this prevents unnecessary cache invalidations and keeps your build context small.
- Consider using 'build-only' stages for running unit tests within the Dockerfile. If the tests fail, the build fails, ensuring only verified code moves to the production stage.
Statistics & data
- Switching from standard images to multi-stage builds frequently reduces final image size by 70% to 90%.
- According to industry benchmarks, container startup times can improve by up to 40% when using minified runtime-only base images.
Key takeaways
- Multi-stage builds allow you to use different base images for building and running your application.
- Smaller images lead to significantly faster CI/CD pipelines and lower cloud costs.
- Security is enhanced by removing unnecessary shells and tools from the final production container.
- Proper layer ordering is essential to take full advantage of Docker's build cache.
Frequently asked questions
Can I have more than two stages?
Yes, you can have as many stages as needed. For example, you might have one stage for linting, one for testing, one for building, and one for the final runtime.
How do I debug a multi-stage build if the final image is empty?
You can use the '--target' flag with 'docker build' to stop the build at a specific stage (e.g., the builder stage) so you can shell into it for debugging.
Are there disadvantages to multi-stage builds?
The only minor disadvantage is slightly more complex Dockerfiles, but the benefits in security and performance almost always outweigh the complexity.
External references
Keep learning with StackForge
New, expert-reviewed tutorials are published regularly. Explore more guides in Cloud & DevOps to deepen your skills.
More Cloud & DevOps guides →
Written & reviewed by
Justin Schmella
Senior Industry Researcher & Content Specialist
Justin Schmella is a senior software engineer and technical educator with more than eight years of hands-on experience shipping production systems across web, cloud, and developer tooling. He began his career as a full-stack developer at a fast-growing SaaS company, where he led the migration of a monolithic application to a modern, service-oriented architecture used by hundreds of thousands of users.
Related tutorials

Automating Multi-Cloud Infrastructure with Terraform and GitHub Actions
Learn how to build a production-ready CI/CD pipeline for cloud infrastructure using Terraform, GitHub Actions, and remote state management.

Mastering Blue-Green Deployments with Terraform and AWS
A comprehensive guide to building resilient, zero-downtime infrastructure using Terraform and the blue-green deployment strategy on AWS.

Optimizing Docker Images for Production: A Practical Guide
Master the art of creating lean, secure, and high-performance Docker images using expert techniques like multi-stage builds and minimal base layers.