Back to Next Js

Next.js Docker Example

examples/with-docker-export-output/README.md

16.2.59.1 KB
Original Source

Next.js Docker Example

A production-ready example demonstrating how to Dockerize Next.js applications using static export mode. This example showcases two different approaches for serving static Next.js sites: Nginx and serve package.

Features

  • Multi-stage Docker build for optimal image size
  • Static export: Fully static HTML/CSS/JavaScript site
  • Two serving options: Nginx (production-grade) and serve (simple Node.js server)
  • Security best practices (non-root user)
  • Slim/Alpine Linux base images for optimal compatibility and smaller size
  • BuildKit cache mounts for faster builds
  • Production-ready configuration with optimized Nginx settings
  • Docker Compose support for easy deployment

Prerequisites

Quick Start with Docker

Nginx is ideal when you need:

  • Production-grade web server
  • Maximum performance and efficiency
  • Advanced caching and compression
  • Smaller Docker images (~50MB)
  • Industry-standard web server

Using Docker Compose

bash
docker compose up nextjs-static-export --build

Access: http://localhost:8080

Using Docker Build

bash
docker build -t nextjs-static-export .
docker run -p 8080:8080 nextjs-static-export

Access: http://localhost:8080

Option 2: Using serve Package

serve is ideal when you need:

  • Simple Node.js-based static file server
  • Quick development/testing deployments
  • Familiar Node.js ecosystem
  • Easy customization

Using Docker Compose

bash
# OR run with serve npm package
docker compose up nextjs-static-export-with-serve --build

Access: http://localhost:3000

Using Docker Build

bash
docker build -t nextjs-static-export-serve -f Dockerfile.serve .
docker run -p 3000:3000 nextjs-static-export-serve

Access: http://localhost:3000

Project Structure

nextjs-docker/
├── app/                    # Next.js App Router directory
│   ├── layout.tsx          # Root layout with metadata
│   ├── page.tsx            # Home page with example content
│   └── globals.css         # Global styles with Tailwind CSS v4
├── public/                 # Static assets
│   └── next.svg           # Next.js logo
├── Dockerfile              # Nginx-based Dockerfile (port 8080)
├── Dockerfile.serve        # serve-based Dockerfile (port 3000)
├── compose.yml             # Docker Compose with both services
├── nginx.conf              # Nginx configuration for static export
├── next.config.ts          # Next.js configuration (static export mode)
├── postcss.config.js       # PostCSS configuration for Tailwind CSS
├── tsconfig.json           # TypeScript configuration
├── package.json            # Dependencies and scripts
└── README.md              # This file

Configuration

Next.js Static Export Mode

The next.config.ts file is configured with output: "export":

typescript
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  output: "export",
  images: {
    unoptimized: true, // Required for static export
  },
};

export default nextConfig;

Static export mode generates a fully static HTML/CSS/JavaScript site that can be served by any static hosting service or web server. When enabled, Next.js generates an out directory containing all static files. This results in:

  • Smaller Docker image (~80MB with Nginx, ~350MB with serve)
  • Faster deployments (no Node.js runtime needed for Nginx)
  • Better CDN compatibility (pure static files)
  • Lower resource usage (Nginx uses minimal memory)

Note: Static export has limitations:

  • No server-side rendering (SSR)
  • No API routes
  • Images must be unoptimized or use external image optimization

Learn more about Next.js static export in the official documentation.

Dockerfile Highlights

Nginx-based (Dockerfile)

  • Multi-stage build: Separates dependency installation (dependencies), build (builder), and runtime (runner) stages
  • Nginx server: Uses nginxinc/nginx-unprivileged:alpine3.22 for serving static files (~50MB final image)
  • BuildKit cache mounts: Speeds up builds by caching package manager stores and Next.js build cache
  • Non-root user: Runs as nginx user for security
  • Production Nginx config: Optimized with gzip compression, caching headers, and security best practices
  • Port: 8080

serve-based (Dockerfile.serve)

  • Multi-stage build: Separates dependency installation (dependencies), build (builder), and runtime (runner) stages
  • Node.js runtime: Uses node:24.13.0-slim for running serve package
  • serve package: Uses [email protected] for serving static files
  • BuildKit cache mounts: Speeds up builds by caching package manager stores and Next.js build cache
  • Non-root user: Runs as node user for security
  • SPA mode: Configured with single-page application support
  • Port: 3000

Node.js version maintenance: Uses Node.js 24.13.0-slim (latest LTS at time of writing). Update the NODE_VERSION ARG to the latest LTS version for security updates.

Nginx image maintenance: Uses nginxinc/nginx-unprivileged:alpine3.22. Update the NGINXINC_IMAGE_TAG ARG to the latest version for security updates.

Why Node.js slim image tag?: The slim variant provides optimal compatibility with npm packages and native dependencies while maintaining a smaller image size (~226MB). Slim uses glibc (standard Linux), ensuring better compatibility than Alpine's musl libc, which can cause issues with some npm packages. This makes it ideal for public examples where reliability and compatibility are priorities.

When to use Alpine?: Consider using node:24.11.1-alpine instead if:

  • Image size is critical: Alpine images are typically ~100MB smaller than slim variants (~110MB base vs ~226MB)
  • Your dependencies are compatible: Your npm packages don't require native binaries that depend on glibc
  • You've tested thoroughly: You've verified all your dependencies work correctly with musl libc
  • Security-focused deployments: Alpine's minimal attack surface can be beneficial for security-sensitive applications

To switch to Alpine, simply change the NODE_VERSION ARG in the Dockerfile to 24.11.1-alpine.

[!IMPORTANT] Version Maintenance:

  • Node.js: This Dockerfile uses Node.js 24.13.0-slim, which was the latest LTS version at the time of writing. To ensure security and stay up-to-date, regularly check and update the NODE_VERSION ARG in the Dockerfile to the latest Node.js LTS version. Check the latest version at Node.js official website and browse available Node.js images on Docker Hub.
  • Nginx: The Nginx Dockerfile uses nginxinc/nginx-unprivileged:alpine3.22. Regularly check and update the NGINXINC_IMAGE_TAG ARG to the latest version. Browse available Nginx images on Docker Hub.
  • serve package: The serve Dockerfile uses [email protected]. Update to the latest version as needed for bug fixes and features.

Package Manager Support

Both Dockerfiles support multiple package managers:

  • npm (via package-lock.json)
  • yarn (via yarn.lock)
  • pnpm (via pnpm-lock.yaml)

The Dockerfiles automatically detect which lockfile is present and use the appropriate package manager.

Deployment

This example can be deployed to any container-based platform:

  • Google Cloud Run
  • AWS ECS/Fargate
  • Azure Container Instances
  • DigitalOcean App Platform
  • Any Kubernetes cluster
  • Vercel (for static export)

Choosing Between Nginx and serve

Use Nginx (Dockerfile) when:

  • Deploying to production
  • Maximum performance is required
  • You need advanced caching and compression
  • Image size is a concern (~50MB vs ~300MB)
  • You want industry-standard web server

Use serve (Dockerfile.serve) when:

  • Quick development/testing deployments
  • You prefer Node.js ecosystem
  • You need easy customization
  • Image size is not a concern

Learn More