Skip to main content

Implementing Multi-Stage Builds

1. Creating Multi-Stage Dockerfile

Stage Description Use Case
FROM node:18-alpine AS builder Initial stage for building application dependencies Optimizing image size by separating build-time and runtime environments
WORKDIR /app Sets working directory in the container Organizing files and commands within a specific path
COPY package*.json . Copies package files to the container Installing dependencies in the builder stage
RUN npm ci --only=production Installs production-only dependencies Reduces image size by excluding devDependencies
FROM node:18-alpine Final stage using minimal runtime image Ensures lightweight, secure runtime environment
WORKDIR /app Sets working directory in the final container Organizes files and commands within a specific path
COPY --from=builder /app/node_modules ./node_modules Copies installed dependencies from builder stage Transfers necessary modules without build tools
COPY . . Copies application code to the container Includes source files in the final image
EXPOSE 3000 Documents the port the application listens on Helps with container orchestration and networking
CMD ["npm", "start"] Defines the default command to run when starting the container Starts the application in the final stage

Example: Multi-Stage Dockerfile

FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci --only=production
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
Note: Multi-stage builds reduce image size significantly by using separate stages for building and running the application.

2. Using Named Build Stages

Stage Name Usage Description
FROM --platform=... AS builder FROM --platform=linux/amd64 AS builder Sets a named build stage for building artifacts
FROM --platform=... AS runtime FROM --platform=linux/amd64 AS runtime Sets a named build stage for the final runtime environment

Example: Multi-stage Dockerfile with Named Stages

FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

FROM node:18 AS runtime
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/index.js"]
Note: Named stages improve readability and allow reusing intermediate build artifacts.

3. Copying Artifacts Between Stages

Source Stage Destination Stage Copy Command Notes
builder production COPY --from=builder /app/dist /app/dist Copy built artifacts from builder stage to production stage
builder testing COPY --from=builder /app/test-results /app/test-results Copy test results from builder to testing stage

Example: Copying Build Artifacts

FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

FROM node:18-alpine AS production
WORKDIR /app
COPY --from=builder /app/dist /app/dist
COPY --from=builder /app/package*.json ./
RUN npm ci --only=production
EXPOSE 3000
CMD ["node", "dist/index.js"]
Note: The --from flag specifies the source stage name or index, and the paths are relative to the build context.

4. Targeting Specific Build Stage

Option Values Description
--target stage-name Specifies the build stage to stop at

Example: Building a Specific Stage

docker build --target production -t myapp:prod .
Note: The --target flag allows you to build only a specific stage in a multi-stage Dockerfile, skipping all previous stages.

5. Optimizing Build Cache Layers

Layer Description Optimization Tip
FROM Base image layer Use multi-stage builds to minimize base image size and avoid unnecessary dependencies.
RUN Command execution layer Combine multiple RUN commands with && to reduce the number of layers.
COPY / ADD File copy layer Use .dockerignore to exclude unnecessary files and directories from the build context.
ENV Environment variable layer Set environment variables early in the Dockerfile to allow for better caching.

Example: Optimizing RUN Commands

# Bad - Multiple RUN commands
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y wget

# Good - Combined RUN commands
RUN apt-get update && \
    apt-get install -y curl wget

Example: Using .dockerignore

# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
README.md
.env

6. Implementing Build-Time Dependencies

Dependency Type Description Usage Example Notes
build-arg Defines build-time variables available only during the build process.
Dockerfile:
ARG NODE_VERSION=18
RUN npm install --production
Useful for versioning or customizing builds without changing the image.
COPY --from Copies files from another stage in the same Dockerfile.
Dockerfile:
FROM node:18 as builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM node:18-alpine
COPY --from=builder /app/node_modules ./node_modules
Enables multi-stage builds to reuse artifacts from previous stages.
RUN with --mount Mounts temporary filesystems or caches during build time.
Dockerfile:
RUN --mount=type=cache,target=/root/.npm \
    npm ci --only=production
Improves build performance by caching dependencies.

Example: Using Build-Time Args

FROM node:18
ARG NODE_ENV=production
ENV NODE_ENV=$NODE_ENV
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Note: Build-time args are not available at runtime and do not persist in the final image.

7. Reducing Final Image Size

Strategy Description Example
Multi-stage builds Use separate build stages to compile and test, then copy only necessary artifacts to the final stage.
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["npm", "start"]
Use minimal base images Choose lightweight base images like Alpine or Slim variants to reduce image size. FROM node:18-alpine
Clean package manager cache Remove package manager caches after installing dependencies to reduce image size.
RUN apk add --no-cache python3 py3-pip
RUN pip install --no-cache-dir some-package

Example: Multi-stage build with size optimization

FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o main

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/main .
CMD ["./main"]
Note: Multi-stage builds allow you to use a full-featured image for building and testing, while shipping only the minimal runtime environment in the final image.

8. Sharing Layers Between Stages

Layer Description Usage
FROM Base image for the stage FROM node:18-alpine AS builder
COPY --from Copy files from another stage COPY --from=builder /app/dist ./dist

Example: Sharing Dependencies

FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
Note: Layers are shared using the --from flag in COPY and ADD instructions.

9. Using External Images in Stages

Stage Base Image Description
FROM node:18-alpine Starts with a Node.js 18 Alpine image as the base
FROM python:3.10-slim Uses Python 3.10 slim image for a lightweight environment
FROM nginx:alpine Uses the official Nginx Alpine image for serving static content

Example: Using External Images in Multi-Stage Build

FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
Note: The AS builder alias allows referencing this stage in later stages using --from=builder.
Warning: Ensure that the external image's architecture matches your target environment (e.g., linux/amd64 vs linux/arm64).

Best Practices for External Images

  • Use official images from Docker Hub when available.
  • Pin to specific tags (e.g., node:18-alpine) rather than using latest.
  • Consider using multi-arch images for cross-platform compatibility.

10. Building Platform-Specific Images

Platform Architecture Dockerfile Instruction Notes
Linux/amd64 amd64 --platform linux/amd64 Default for most Docker builds
Linux/arm64 arm64 --platform linux/arm64 Used for ARM-based systems like Apple Silicon
Windows/amd64 amd64 --platform windows/amd64 For Windows containers

Example: Building for ARM64

docker build --platform linux/arm64 -t myapp:arm64 .
Note: Platform-specific builds require --platform flag to ensure correct architecture and dependencies.
Warning: Cross-platform builds may not work if base images or dependencies are not available for the target platform.

11. Creating Development vs Production Stages

Stage Description Base Image Use Case
development Includes all dependencies for building and running the application in a development environment. node:18-alpine Local development, testing, debugging
production Minimal image optimized for running the application in production. node:18-alpine Deployment, reduced attack surface, faster startup

Example: Multi-stage Dockerfile

FROM node:18-alpine AS development
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=development
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]

FROM node:18-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=development /app/src ./src
EXPOSE 3000
CMD ["npm", "start"]
Note: Use --only=production to avoid installing devDependencies in the production stage.

Key Benefits

  • Smaller production image size
  • Reduced security risks from dev tools
  • Optimized build and runtime environments

12. Using Scratch Base Image

Property Value Description
FROM scratch Minimal base image with no OS or packages
Size ~2.5MB Smallest possible image size
Use Case Static binaries Best for compiled Go, Rust apps

Example: Go App with Scratch

FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

FROM scratch
COPY --from=builder /app/main /main
ENTRYPOINT ["/main"]
Note: No shell, package manager, or filesystem tools in scratch image.
Warning: Requires all dependencies to be statically linked or copied into image.

Was this article helpful?