Optimize Docker Images with Multi-Stage Builds
Refactor a bloated single-stage Node.js Dockerfile into a multi-stage build and shrink the production image by over 90 percent.

Lab overview
A Docker image is more than the application that runs inside it. By default, it also carries every compiler, package manager, dev dependency, and intermediate file that was needed to assemble the app, even though none of that is required to actually run it in production. The result is bloated images that are slow to pull on cold nodes, expensive to store in registries, and packed with a wide attack surface of tools an intruder can pivot through. Multi-stage builds solve this problem by letting a single Dockerfile describe two distinct phases: a heavy "builder" stage that installs SDKs and compiles or installs dependencies, and a lean "runtime" stage that copies only the finished artifacts into a minimal base image like alpine, distroless, or scratch. The Open Container Initiative reports that images built with multi-stage workflows ship with roughly 40 percent fewer known vulnerabilities than equivalent single-stage builds, on top of dramatic size reductions of 80 to 99 percent across common runtimes.
In this lab, you will start with a deliberately bloated single-stage Node.js Express image and refactor it into a multi-stage Dockerfile. You will use FROM ... AS builder to install production dependencies in a build stage, COPY --from=builder to cherry-pick only those artifacts into a runtime stage, and then switch the runtime base to a distroless image. By the end you will be able to evaluate base-image trade-offs (Debian versus Alpine versus distroless) and apply the multi-stage pattern to your own services.
Objectives
By completing this intermediate lab, you will be able to:
- Build the provided single-stage Dockerfile and measure the bloated image footprint with
docker image ls. - Refactor the Dockerfile into a two-stage build using
FROM ... AS builderand a minimal runtime stage. - Use
COPY --from=builderto copy only production node_modules and source into the final image. - Swap the runtime base image to
node:22-alpineand compare the content size with the single-stage image. - Move the runtime base to a distroless Node image and choose the right trade-off for production.
Who is this lab for?
This lab is designed for:
- Developers who can already write a basic Dockerfile and want to ship smaller, more secure production images.
- Platform engineers tuning their registry footprint and node-pull-time SLOs.
- Security-minded engineers reducing the attack surface of their container fleet.
Verified against your live environment
An automated validation engine inspects your actual resources and configurations as you work. Completion means the task was performed, not multiple choice, real-world proficiency.
More labs like this
Creating Multi-Environment Workflows in GitHub Actions
Build automated deployment pipelines with GitHub Actions, configure approval gates, and deploy containerized apps to dev and production Azure environments.
Containerize and Deploy a Multi-Container Application to Azure Container Apps
Build Docker images for a restaurant menu app, push to ACR, and deploy frontend and backend containers to Azure Container Apps.
Write Dockerfiles and Build Custom Images
Author a single-stage Dockerfile from scratch, build a tagged image for a static site, and observe layer caching in action.
Related reading
Environment
Every lab includes
- Real environment, pre-credentialed
- Automated checks on every step
- Isolated sandbox, auto cleanup
- AI-recommended next steps
Lab curriculum
- 01
Getting Started with Docker IDE
- 02
Build and Measure the Bloated Single-Stage Image
1 automated check
- 03
Refactor the Dockerfile into a Multi-Stage Build
1 automated check
- 04
Shrink the Runtime Image with a Minimal Base
1 automated check
Skills validated
Not the lab you were looking for?
Browse 200+ hands-on labs across AWS, Azure, Kubernetes, Docker, and cloud security.