Containers aren't just for backend services. Containerizing your Next.js app improves consistency, simplifies deployment, and enables advanced patterns. Here's the complete guide.
Multi-Stage Builds for Smaller Images
Production images should be minimal. Multi-stage builds let you use full Node for building, then copy only what's needed to a slim final image.
# Stage 1: Dependencies
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
# Stage 2: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm build
# Stage 3: Production
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
CMD ["node", "server.js"]Development Containers
VS Code Dev Containers provide consistent development environments. Everyone gets the same Node version, same tools, same extensions.
// .devcontainer/devcontainer.json
{
"name": "Next.js Dev",
"image": "mcr.microsoft.com/devcontainers/typescript-node:20",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
},
"postCreateCommand": "pnpm install",
"customizations": {
"vscode": {
"extensions": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
}
}
}Layer Caching
Order Dockerfile commands from least to most frequently changing. Copy package.json before source code—dependencies don't change every commit.
Docker Compose for Local Development
version: '3.8'
services:
web:
build:
context: .
target: deps
volumes:
- .:/app
- /app/node_modules
ports:
- "3000:3000"
command: pnpm dev
depends_on:
- db
db:
image: postgres:15
environment:
POSTGRES_DB: app
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:Kubernetes vs Serverless
Kubernetes: Full control, complex operations, best for large scale. Serverless (Vercel, Fly.io): Simple deployment, automatic scaling, per-request pricing.
For most Next.js apps, serverless is simpler. Use Kubernetes when you need specific infrastructure control or have complex multi-service architectures.
Containerization provides consistency from development to production. Start with the basics—a good Dockerfile and docker-compose—and add complexity as needed.
