
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!