Isaac.

devops

Docker Multi-Stage Builds

Optimize Docker images with multi-stage builds.

By Emem IsaacAugust 30, 20223 min read
#docker#multi-stage builds#optimization#containers
Share:

A Simple Analogy

Multi-stage Docker builds are like construction cleanup. You keep temporary scaffolding during building, but remove it before the final product ships.


Why Multi-Stage Builds?

  • Smaller images: Remove build artifacts
  • Faster deployment: Less bandwidth
  • Security: Don't ship source code
  • Separation: Build vs runtime dependencies
  • Cost savings: Storage and network

Node.js Example

# Stage 1: Build
FROM node:18-alpine AS builder
WORKDIR /app

# Copy package files
COPY package*.json ./

# Install all dependencies
RUN npm ci

# Copy source
COPY . .

# Build
RUN npm run build

# Stage 2: Runtime
FROM node:18-alpine
WORKDIR /app

# Copy only production dependencies
COPY package*.json ./
RUN npm ci --only=production

# Copy built app from builder
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/public ./public

EXPOSE 3000
CMD ["node", "dist/index.js"]

.NET Example

# Stage 1: Build
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src

COPY ["MyApp.csproj", "."]
RUN dotnet restore "MyApp.csproj"

COPY . .
RUN dotnet build "MyApp.csproj" -c Release -o /app/build

# Stage 2: Publish
FROM build AS publish
RUN dotnet publish "MyApp.csproj" -c Release -o /app/publish

# Stage 3: Runtime
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=publish /app/publish .

EXPOSE 80
ENTRYPOINT ["dotnet", "MyApp.dll"]

Advanced: Conditional Stages

# Stage 1: Development
FROM node:18-alpine AS dev
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npm", "run", "dev"]

# Stage 2: Test
FROM dev AS test
RUN npm run test

# Stage 3: Build
FROM test AS builder
RUN npm run build

# Stage 4: Production
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/index.js"]

Image Size Comparison

Without multi-stage:
- npm install (all deps)        500MB
- node_modules                  400MB
- source code                   50MB
- dist (built files)            20MB
TOTAL: 970MB

With multi-stage:
- dist (from builder)           20MB
- npm ci --only=production      300MB
- node_modules (prod only)      280MB
TOTAL: 300MB (69% reduction!)

Build Arguments

FROM node:18-alpine AS builder
ARG NODE_ENV=production
ARG BUILD_DATE
ARG VCS_REF

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM node:18-alpine
LABEL org.opencontainers.image.created=$BUILD_DATE
LABEL org.opencontainers.image.revision=$VCS_REF

COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm ci --only=production

EXPOSE 3000
CMD ["node", "dist/index.js"]

Build with:

docker build \
  --build-arg NODE_ENV=production \
  --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
  --build-arg VCS_REF=$(git rev-parse --short HEAD) \
  -t myapp:latest .

Best Practices

  1. Order stages: Build → test → runtime
  2. Copy selectively: Only needed files
  3. Use .dockerignore: Exclude unnecessary files
  4. Cache layers: Stable files first
  5. Minimize runtime: Keep final stage small

Related Concepts

  • Layer caching
  • .dockerignore files
  • Image registry optimization
  • Container security

Summary

Multi-stage builds dramatically reduce image sizes by separating build and runtime environments. Use them to optimize deployment speed and reduce storage costs.

Share:

Written by Emem Isaac

Expert Software Engineer with 15+ years of experience building scalable enterprise applications. Specialized in ASP.NET Core, Azure, Docker, and modern web development. Passionate about sharing knowledge and helping developers grow.

Ready to Build Something Amazing?

Let's discuss your project and explore how my expertise can help you achieve your goals. Free consultation available.

💼 Trusted by 50+ companies worldwide | ⚡ Average response time: 24 hours