Creating Docker images is a necessary skill in current software development. However, oversized images can lengthen build times, hinder deployments, and add unneeded vulnerabilities. Multi-stage Dockerfiles are a technique for creating lean, efficient, and secure images.
A multi-stage Dockerfile allows you to include multiple FROM statements in a single file. Each FROM statement generates a new build stage, and you can move artifacts (such as binaries or compiled files) from one stage to another. This helps to isolate the building and runtime environments, resulting in smaller, cleaner images.
Let us demonstrate the difference with a real-world example. Here is a standard Dockerfile for a Go application:
# Build stage FROM golang:1.21 AS builder WORKDIR /app COPY . . RUN go build -o main . # Final stage FROM alpine:3.19 COPY --from=builder /app/main /main CMD ["/main"]
This creates a huge image including the whole Go toolchain. Now, let's change it with multi-stage builds:
# Build stage FROM golang:1.21 AS builder WORKDIR /app COPY . . RUN go build -o main . # Final stage FROM alpine:3.19 COPY --from=builder /app/main /main CMD ["/main"]
What's the difference? The first image could be 1GB or more, but the multi-stage version could be as small as 15MB!
A multi-stage Dockerfile contains:
# Use Node.js LTS (Long Term Support) as the base image FROM node:lts-bullseye-slim as base # Install necessary dependencies RUN apt-get update && apt-get install -y \ openssl \ dumb-init \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # Set working directory WORKDIR /usr/src/app # Install global dependencies RUN npm install -g typescript # Build stage FROM base as builder # Copy package.json and package-lock.json (if available) COPY package*.json ./ # Install all dependencies (including devDependencies) RUN npm install --legacy-peer-deps # Copy prisma schema and generate client COPY prisma ./prisma RUN npx prisma generate # Copy tsconfig.json and source code COPY tsconfig.json ./ COPY src ./src # Build the application RUN npm run build # Production stage FROM base as production # Set NODE_ENV to production ENV NODE_ENV production # Copy package.json and package-lock.json (if available) COPY package*.json ./ # Install only production dependencies RUN npm install --only=production --legacy-peer-deps # Copy prisma schema and generate client COPY prisma ./prisma RUN npx prisma generate # Copy built application from builder stage COPY --from=builder /usr/src/app/dist ./dist # Copy any additional necessary files (e.g., views, public assets) COPY views ./views # Create a non-root user RUN useradd -m appuser # Create logs directory and set permissions RUN mkdir logs && chown appuser:appuser logs # Switch to non-root user USER appuser # Expose the port the app runs on EXPOSE ${PORT} # Set default values for Redis configuration ENV REDIS_HOST=redis ENV REDIS_PORT=6379 # Start the application CMD ["dumb-init", "node", "dist/index.js"]
# Stage 1: Build the application FROM node:18-alpine AS builder # Set working directory WORKDIR /app # Install dependencies COPY package.json package-lock.json ./ RUN npm install --frozen-lockfile # Copy the rest of the application code COPY . . # Build the application RUN npm run build # Stage 2: Production image FROM node:18-alpine AS runner # Set working directory WORKDIR /app # Copy only necessary files from the builder stage COPY --from=builder /app/package.json /app/package-lock.json ./ COPY --from=builder /app/.next ./.next COPY --from=builder /app/public ./public COPY --from=builder /app/next.config.mjs ./next.config.mjs # Install only production dependencies RUN npm install --only=production --frozen-lockfile # Expose the port that the app runs on EXPOSE ${PORT} # Start the application CMD ["npm", "start"]
# Build stage FROM maven:3.9-eclipse-temurin-17 AS builder WORKDIR /app COPY pom.xml . COPY src ./src RUN mvn clean package -DskipTests # Final stage FROM eclipse-temurin:17-jre-alpine COPY --from=builder /app/target/*.jar app.jar CMD ["java", "-jar", "app.jar"]
Example .dockerignore:
node_modules dist .git
FROM node:18 AS build-stage
At DevelopersMonk, we share tutorials, tips, and insights on modern programming frameworks like React, Next.js, Spring Boot, and more. Join us on our journey to simplify coding and empower developers worldwide!