Back to Blog
10 min read

Dockerizing Your Next.js Application for Production

DockerDevOpsNext.js

Dockerizing Your Next.js Application for Production

Containerization with Docker has become the standard for deploying modern web applications. It ensures consistency across environments, simplifies deployment, and makes scaling easier. Here's how to properly dockerize your Next.js application for production.

Why Docker for Next.js?

Consistency: Your application runs the same way in development, staging, and production.

Isolation: Dependencies and runtime are contained, reducing conflicts.

Scalability: Easy to spin up multiple containers for load balancing.

CI/CD Integration: Works seamlessly with modern deployment pipelines.

Understanding Next.js Build Modes

Next.js supports different rendering modes:

  • Static Export: Pre-rendered static HTML
  • Standalone: Optimized production build
  • Server-Side Rendering (SSR): Rendered on each request
  • Incremental Static Regeneration (ISR): Hybrid approach

For Docker, we'll focus on the standalone build mode, which is optimized for containerized deployments.

Basic Dockerfile

Let's start with a production-ready Dockerfile:

DOCKERFILE

# Stage 1: Dependencies

FROM node:20-alpine AS deps

WORKDIR /app

Copy package files

COPY package.json package-lock.json* ./

Install dependencies

RUN npm ci

Stage 2: Builder

FROM node:20-alpine AS builder

WORKDIR /app

Copy dependencies from deps stage

COPY --from=deps /app/node_modules ./node_modules

COPY . .

Set environment variables for build

ENV NEXT_TELEMETRY_DISABLED 1

Build the application

RUN npm run build

Stage 3: Runner

FROM node:20-alpine AS runner

WORKDIR /app

ENV NODE_ENV production

ENV NEXT_TELEMETRY_DISABLED 1

Create non-root user

RUN addgroup --system --gid 1001 nodejs

RUN adduser --system --uid 1001 nextjs

Copy necessary files

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

COPY --from=builder /app/.next/standalone ./

COPY --from=builder /app/.next/static ./.next/static

Set ownership

RUN chown -R nextjs:nodejs /app

USER nextjs

EXPOSE 3000

ENV PORT 3000

ENV HOSTNAME "0.0.0.0"

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

Enabling Standalone Output

To use the standalone build, update your next.config.ts:

TYPESCRIPT

import type { NextConfig } from 'next';

const nextConfig: NextConfig = {

output: 'standalone',

};

export default nextConfig;

Multi-Stage Build Benefits

The Dockerfile above uses multi-stage builds:

1. deps: Installs dependencies (can be cached separately)

2. builder: Builds the application

3. runner: Minimal runtime image with only production files

This reduces final image size from ~1GB to ~150MB.

Docker Compose for Development

For local development, create a docker-compose.yml:

YAML

version: '3.8'

services:

app:

build:

context: .

dockerfile: Dockerfile.dev

ports:

  • "3000:3000"

volumes:

  • .:/app
  • /app/node_modules
  • /app/.next

environment:

  • NODE_ENV=development

Environment Variables

Handle environment variables properly:

DOCKERFILE

# In your Dockerfile

ARG NEXT_PUBLIC_API_URL

ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL

ARG DATABASE_URL

ENV DATABASE_URL=$DATABASE_URL

Or use a .env file with docker-compose:

YAML

services:

app:

env_file:

  • .env.production

.dockerignore

Create a .dockerignore file to exclude unnecessary files:

TEXT

node_modules

.next

.git

.gitignore

README.md

.env.local

.env*.local

npm-debug.log*

.DS_Store

Health Checks

Add health checks to your Dockerfile:

DOCKERFILE

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \

CMD node healthcheck.js || exit 1

Building and Running

Build the image:

BASH

docker build -t my-nextjs-app .

Run the container:

BASH

docker run -p 3000:3000 my-nextjs-app

Production Considerations

Security

  • Use non-root user (already in Dockerfile above)
  • Keep base images updated
  • Scan images for vulnerabilities
  • Use secrets management (Docker secrets, Kubernetes secrets)

Performance

  • Use Alpine Linux base images (smaller, faster)
  • Enable Next.js standalone mode
  • Implement proper caching strategies
  • Use CDN for static assets

Monitoring

  • Add logging (structured logs)
  • Implement health check endpoints
  • Monitor container resource usage
  • Set up alerting

Common Pitfalls

1. Not using .dockerignore: Leads to large images and slow builds

2. Installing dev dependencies in production: Increases image size

3. Running as root: Security risk

4. Not using multi-stage builds: Unnecessarily large images

5. Hardcoding environment variables: Makes deployment inflexible

Advanced: Docker Compose with Database

For full-stack applications:

YAML

version: '3.8'

services:

app:

build: .

ports:

  • "3000:3000"

environment:

  • DATABASE_URL=postgresql://user:pass@db:5432/mydb

depends_on:

  • db

db:

image: postgres:15-alpine

environment:

  • POSTGRES_USER=user
  • POSTGRES_PASSWORD=pass
  • POSTGRES_DB=mydb

volumes:

  • postgres_data:/var/lib/postgresql/data

volumes:

postgres_data:

Conclusion

Dockerizing your Next.js application provides consistency, scalability, and easier deployment. The multi-stage build approach keeps images small and secure, while proper configuration ensures optimal performance.

Remember: containerization is just one part of a production-ready deployment. You'll also need proper monitoring, logging, and CI/CD pipelines.

---

*Need help dockerizing your application or setting up production infrastructure? Get in touch to discuss your deployment needs.*

Enjoyed this article?

If you found this helpful, let's discuss how we can help with your next project.

Book a call