Docker Configuration

Patterns for containerizing Next.js applications with Docker.

Overview#

Docker enables consistent deployments across environments. This pattern covers:

  • Multi-stage Dockerfile
  • Docker Compose for development
  • Production configuration
  • Health checks
  • Prisma in Docker

Prerequisites#

# Docker Desktop or Docker Engine installed docker --version

Code Example#

Multi-Stage Dockerfile#

1# Dockerfile 2FROM node:20-alpine AS base 3 4# Install dependencies only when needed 5FROM base AS deps 6RUN apk add --no-cache libc6-compat 7WORKDIR /app 8 9COPY package.json package-lock.json* ./ 10RUN npm ci 11 12# Rebuild source code only when needed 13FROM base AS builder 14WORKDIR /app 15COPY --from=deps /app/node_modules ./node_modules 16COPY . . 17 18ENV NEXT_TELEMETRY_DISABLED 1 19 20RUN npm run build 21 22# Production image 23FROM base AS runner 24WORKDIR /app 25 26ENV NODE_ENV production 27ENV NEXT_TELEMETRY_DISABLED 1 28 29RUN addgroup --system --gid 1001 nodejs 30RUN adduser --system --uid 1001 nextjs 31 32COPY --from=builder /app/public ./public 33 34# Standalone output 35COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ 36COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static 37 38USER nextjs 39 40EXPOSE 3000 41ENV PORT 3000 42ENV HOSTNAME "0.0.0.0" 43 44CMD ["node", "server.js"]

Next.js Standalone Configuration#

1// next.config.js 2/** @type {import('next').NextConfig} */ 3const nextConfig = { 4 output: 'standalone' 5} 6 7module.exports = nextConfig

Docker Compose for Production#

1# docker-compose.yml 2version: '3.8' 3 4services: 5 app: 6 build: 7 context: . 8 dockerfile: Dockerfile 9 ports: 10 - "3000:3000" 11 environment: 12 - DATABASE_URL=postgresql://postgres:password@db:5432/myapp 13 - REDIS_URL=redis://redis:6379 14 depends_on: 15 db: 16 condition: service_healthy 17 redis: 18 condition: service_started 19 healthcheck: 20 test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"] 21 interval: 30s 22 timeout: 10s 23 retries: 3 24 start_period: 10s 25 26 db: 27 image: postgres:15-alpine 28 volumes: 29 - postgres_data:/var/lib/postgresql/data 30 environment: 31 - POSTGRES_USER=postgres 32 - POSTGRES_PASSWORD=password 33 - POSTGRES_DB=myapp 34 ports: 35 - "5432:5432" 36 healthcheck: 37 test: ["CMD-SHELL", "pg_isready -U postgres"] 38 interval: 10s 39 timeout: 5s 40 retries: 5 41 42 redis: 43 image: redis:7-alpine 44 volumes: 45 - redis_data:/data 46 ports: 47 - "6379:6379" 48 49volumes: 50 postgres_data: 51 redis_data:

Development Docker Compose#

1# docker-compose.dev.yml 2version: '3.8' 3 4services: 5 app: 6 build: 7 context: . 8 dockerfile: Dockerfile.dev 9 volumes: 10 - .:/app 11 - /app/node_modules 12 ports: 13 - "3000:3000" 14 environment: 15 - NODE_ENV=development 16 - DATABASE_URL=postgresql://postgres:password@db:5432/myapp_dev 17 command: npm run dev 18 19 db: 20 image: postgres:15-alpine 21 environment: 22 - POSTGRES_PASSWORD=password 23 - POSTGRES_DB=myapp_dev 24 ports: 25 - "5432:5432" 26 volumes: 27 - dev_postgres_data:/var/lib/postgresql/data 28 29volumes: 30 dev_postgres_data:

Development Dockerfile#

1# Dockerfile.dev 2FROM node:20-alpine 3 4WORKDIR /app 5 6# Install dependencies 7COPY package.json package-lock.json* ./ 8RUN npm install 9 10# Copy source 11COPY . . 12 13# Generate Prisma client 14RUN npx prisma generate 15 16EXPOSE 3000 17 18CMD ["npm", "run", "dev"]

.dockerignore#

# .dockerignore node_modules .next .git .gitignore *.md .env* !.env.example .DS_Store coverage .nyc_output dist .turbo

Health Check Endpoint#

1// app/api/health/route.ts 2import { prisma } from '@/lib/db' 3 4export async function GET() { 5 try { 6 // Check database connection 7 await prisma.$queryRaw`SELECT 1` 8 9 return Response.json({ 10 status: 'healthy', 11 timestamp: new Date().toISOString(), 12 checks: { 13 database: 'ok' 14 } 15 }) 16 } catch (error) { 17 return Response.json( 18 { 19 status: 'unhealthy', 20 error: 'Database connection failed', 21 timestamp: new Date().toISOString() 22 }, 23 { status: 503 } 24 ) 25 } 26}

Prisma in Docker#

1# Generate Prisma client during build 2FROM base AS builder 3WORKDIR /app 4 5# Copy Prisma schema first 6COPY prisma ./prisma/ 7 8# Generate Prisma client 9RUN npx prisma generate 10 11# Copy dependencies and source 12COPY --from=deps /app/node_modules ./node_modules 13COPY . . 14 15RUN npm run build

Database Migration Script#

1#!/bin/bash 2# scripts/docker-migrate.sh 3 4set -e 5 6echo "Waiting for database..." 7until pg_isready -h db -U postgres; do 8 sleep 1 9done 10 11echo "Running migrations..." 12npx prisma migrate deploy 13 14echo "Migrations complete!"
1# docker-compose.yml - Add migration service 2services: 3 migrate: 4 build: 5 context: . 6 dockerfile: Dockerfile 7 environment: 8 - DATABASE_URL=postgresql://postgres:password@db:5432/myapp 9 depends_on: 10 db: 11 condition: service_healthy 12 command: npx prisma migrate deploy 13 restart: on-failure

Docker Build Arguments#

1# Dockerfile with build args 2ARG NODE_VERSION=20 3FROM node:${NODE_VERSION}-alpine AS base 4 5ARG NEXT_PUBLIC_API_URL 6ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} 7 8# ... rest of Dockerfile
# Build with args docker build \ --build-arg NEXT_PUBLIC_API_URL=https://api.example.com \ -t myapp:latest .

Multi-Architecture Build#

# Build for multiple platforms docker buildx build \ --platform linux/amd64,linux/arm64 \ -t myapp:latest \ --push .

Usage Instructions#

  1. Create the Dockerfile with multi-stage build
  2. Configure next.config.js with output: 'standalone'
  3. Create docker-compose.yml for your services
  4. Add health check endpoints
  5. Set up database migrations

Best Practices#

  • Multi-stage builds - Minimize final image size
  • Non-root user - Run as non-privileged user
  • Health checks - Enable container orchestration
  • .dockerignore - Exclude unnecessary files
  • Layer caching - Order commands for cache efficiency
  • Environment variables - Never bake secrets into images
  • Standalone output - Use Next.js standalone mode