← All posts
June 10, 2026 · DevOps

Dockerizing and Optimizing a Next.js Application

Learn how to write a highly optimized multi-stage Dockerfile for Next.js in production to reduce image size by 90% and strengthen security.

Introduction — The Problem with Naive Dockerization

Next.js is the leading React framework for SSR (Server-Side Rendering) and SSG (Static Site Generation) applications. However, when containerizing for production using Docker, the naive approach of copying the full project and running npm install produces images often exceeding 1.4 GB, slow to deploy and filled with unnecessary development tools.

For enterprise-grade Cloud/Kubernetes infrastructure, the DevOps pipeline goal is to produce a Docker image that is:

  1. Ultra-lightweight (under 120 MB).
  2. Secure (running without root privileges).
  3. CI/CD cache-optimized (via multi-stage layer separation).

1. Analyzing a Naive Dockerfile (Insecure & Inefficient)

❌ Single-Stage Dockerfile (Do Not Use in Production)

# ❌ PROBLEM 1: Full-fat Node.js base image (contains Python, gcc, and unnecessary tools)
FROM node:20

WORKDIR /app

# ❌ PROBLEM 2: Copying entire project before install invalidates Docker cache on every change
COPY . .

# ❌ PROBLEM 3: Installs ALL devDependencies (TypeScript, ESLint, Jest, Tailwind CLI...)
RUN npm install

RUN npm run build

EXPOSE 3000

# ❌ PROBLEM 4: Application runs as ROOT user inside the container!
# If a RCE vulnerability is exploited, the attacker controls the container filesystem with maximum privileges.
CMD ["npm", "run", "start"]

Resulting image size: ~1.45 GB — slow to push/pull, and dangerously over-privileged.


2. Pre-requisite: Enable output: 'standalone' in Next.js

Before writing the Dockerfile, enable the standalone output mode in next.config.js. This feature analyzes your dependency tree and isolates only what's needed to run the production Node.js server.

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // ✅ Generates a minimal standalone build in .next/standalone
  output: 'standalone',
};

module.exports = nextConfig;

3. Optimized Multi-Stage Dockerfile

The Multi-Stage Build pattern separates construction into 3 isolated stages:

  1. deps — Install production dependencies.
  2. builder — Compile TypeScript and run Next.js build.
  3. runner — Minimal production image with only compiled artifacts.

✅ Professional Multi-Stage Dockerfile

# ==========================================
# STAGE 1: Dependency Installation (deps)
# ==========================================
FROM node:20-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

# Copy only package manifests to maximize Docker layer cache reuse
COPY package.json package-lock.json ./
RUN npm ci

# ==========================================
# STAGE 2: Next.js Build (builder)
# ==========================================
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

ENV NEXT_TELEMETRY_DISABLED 1

RUN npm run build

# ==========================================
# STAGE 3: Minimal Production Image (runner)
# ==========================================
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1

# ✅ SECURITY: Create dedicated non-root system user and group
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

RUN mkdir .next
RUN chown nextjs:nodejs .next

# ✅ Copy only the minimal standalone build (no full node_modules, no source code)
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

# ✅ Switch to non-root user for runtime
USER nextjs

EXPOSE 3000

ENV PORT 3000
ENV HOSTNAME "0.0.0.0"

# Start with Node.js directly (not via npm — faster startup, smaller footprint)
CMD ["node", "server.js"]

4. Size & Security Comparison

| Metric | Naïve Dockerfile | Optimized Multi-Stage | |---|---|---| | Image Size | ~1.45 GB | ~118 MB (-92%) | | Runtime User | root (Dangerous) | nextjs (Non-Root) | | CI/CD Deploy Time | 3–5 minutes | < 30 seconds | | CVE Attack Surface | High (dev tools included) | Minimal (Alpine base) |


.next
node_modules
.git
.gitignore
README.md
.env*.local
*.md

Add this to your project root to prevent Docker from copying unnecessary local files into the build context, further speeding up builds.