DEV Community

Atlas Whoff
Atlas Whoff

Posted on

Docker Multi-Stage Builds: Smaller Images, Faster Deploys

Docker Multi-Stage Builds: Smaller Images, Faster Deploys

A naive Node.js Docker image includes dev dependencies, build tools, and TypeScript source — 1GB+. Multi-stage builds compile in one stage and copy only the artifacts to the final image — 150MB.

Single Stage (What Not to Do)

FROM node:20
WORKDIR /app
COPY . .
RUN npm install        # Dev deps included
RUN npm run build      # Build tools included
CMD ["node", "dist/index.js"]
# Image size: ~1.2GB
Enter fullscreen mode Exit fullscreen mode

Multi-Stage Build

# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci                    # Install ALL deps (including dev)
COPY . .
RUN npm run build             # Compile TypeScript

# Stage 2: Production
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

COPY package*.json ./
RUN npm ci --omit=dev         # Production deps only
COPY --from=builder /app/dist ./dist  # Only compiled output

USER node                     # Don't run as root
CMD ["node", "dist/index.js"]
# Image size: ~180MB
Enter fullscreen mode Exit fullscreen mode

Next.js Dockerfile

FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

# Next.js standalone output
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public

USER node
EXPOSE 3000
CMD ["node", "server.js"]
# Image size: ~120MB
Enter fullscreen mode Exit fullscreen mode

Enable Standalone Output (Next.js)

// next.config.ts
export default {
  output: 'standalone',
};
Enter fullscreen mode Exit fullscreen mode

.dockerignore

node_modules
.next
.git
*.md
.env*
coverage
Enter fullscreen mode Exit fullscreen mode

Layer Caching Optimization

# Copy package files FIRST — changes rarely
COPY package*.json ./
RUN npm ci

# Copy source AFTER — changes often, invalidates cache from here
COPY . .
RUN npm run build
# Result: npm install only reruns when package.json changes
Enter fullscreen mode Exit fullscreen mode

Docker Compose for Local Dev

# docker-compose.yml
services:
  app:
    build: .
    ports: ['3000:3000']
    environment:
      - DATABASE_URL=postgresql://postgres:postgres@db:5432/mydb
    depends_on: [db]

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:
Enter fullscreen mode Exit fullscreen mode

Docker deployment is pre-configured in the Ship Fast Skill Pack/deploy skill generates optimized Dockerfiles for your stack. $49 at whoffagents.com.

Top comments (0)