Engineering Note
Infrastructure

Fixing Next.js + Docker for Production

Hardening Build and Runtime Behavior

8 min read
BeginnerInfrastructure

Introduction

Running a Next.js application in production is not just about writing code. It’s about packaging it efficiently. Docker is commonly used for deployment, but a naive setup often results in large images, slow builds, and inefficient runtime behavior.

The goal is to create a lightweight, predictable, and production-ready container that only includes what is necessary to run the application.

The Problem

A typical mistake is using a single-stage Dockerfile that installs everything and ships it to production.


FROM node:18
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
CMD ["npm", "start"]

This approach includes development dependencies, increases image size, and slows down deployment.

  • Large Docker image size
  • Slow build times
  • Unnecessary files in production
  • Security risks from unused dependencies

System Design / Approach

The solution is to use a multi-stage Docker build and leverage Next.js standalone output.

  • Build the app in one stage
  • Copy only required files to final image
  • Exclude dev dependencies
  • Keep runtime image minimal

This reduces image size and improves performance without changing application logic.

Implementation

Step 1: Enable Standalone Output

Configure Next.js to generate a standalone build.


// next.config.js
module.exports = {
  output: "standalone",
};

This allows Docker to run the app without installing full dependencies.

Step 2: Use Multi-Stage Build

Separate build and runtime environments.


FROM node:18 AS builder
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build

FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

CMD ["node", "server.js"]

The final image only contains compiled output, making it much smaller.

Step 3: Handle Environment Variables

Separate build-time and runtime environment variables.


ENV NODE_ENV=production

Avoid hardcoding secrets during build time.

Trade-offs

Approach Benefit Cost
Multi-stage build Smaller image size More setup complexity
Standalone output Simplified deployment Requires config changes

Real-World Impact

  • Reduced Docker image size significantly
  • Faster deployment times
  • Improved startup performance
  • Cleaner production environment

Key Takeaways

Production Docker builds should be minimal and avoid unnecessary dependencies

Multi-stage builds significantly reduce final image size

Environment variables must be handled differently in build-time and runtime

Next.js standalone output simplifies deployment in containers

Incorrect Docker configuration can lead to large images and slow startup times

Future Improvements

Use distroless or alpine images to further reduce image size

Introduce container health checks for reliability

Implement CI/CD pipelines for automated Docker builds and deployments

Add caching layers for faster builds using Docker layer caching

Use container orchestration tools like Kubernetes for scaling

Fixing Next.js + Docker for Production | Tushar Kanti Dey