A complete DevOps playbook for containerizing Next.js 15 App Router applications using standalone multi-stage Dockerfiles, Docker Compose orchestrations, and Kubernetes (K8s) EKS deployment manifests.
output: 'standalone' and Alpine Node.js base images to shrink Docker payload size down to ~120MB.nextjs:nodejs) for zero-trust container security.To minimize container size, configure next.config.ts (or next.config.js) to output standalone server bundles:
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "standalone",
reactStrictMode: true,
poweredByHeader: false, // Security hardening
};
export default nextConfig;
Multi-stage builds separate build dependencies from runtime payloads, ensuring dev SDKs never leak into production containers.
# ============================================
# STAGE 1: Dependency Resolver
# ============================================
FROM node:20-alpine AS deps
RUN apk add --no-libc-dev libc6-compat
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# ============================================
# STAGE 2: Standalone Builder
# ============================================
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Environment build args for public client routes
ARG NEXT_PUBLIC_SITE_URL
ENV NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL}
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
# ============================================
# STAGE 3: Production Runner (Hardened)
# ============================================
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
# Create unprivileged system user for container security
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Set ownership permissions for standalone output
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]
Deploy your Next.js microservice into a Kubernetes cluster using clean declarative YAML manifests.
# k8s-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nextjs-microservice
namespace: production
labels:
app: nextjs-web
spec:
replicas: 3
selector:
matchLabels:
app: nextjs-web
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: nextjs-web
spec:
containers:
- name: nextjs-app
image: registry.ajitdev.com/nextjs-web:v1.2.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3000
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
# Health Probes for Zero-Downtime Rollouts
livenessProbe:
httpGet:
path: /api/health
port: 3000
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
envFrom:
- configMapRef:
name: nextjs-config
- secretRef:
name: nextjs-secrets
---
apiVersion: v1
kind: Service
metadata:
name: nextjs-service
namespace: production
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 3000
protocol: TCP
selector:
app: nextjs-web
Create a lightweight health route at app/api/health/route.ts for Kubernetes liveness checks:
// app/api/health/route.ts
import { NextResponse } from "next/server";
export async function GET() {
return NextResponse.json(
{
status: "healthy",
timestamp: new Date().toISOString(),
uptime: process.uptime(),
},
{ status: 200 }
);
}
USER nextjs prevents malicious exploits from gaining host root access.memory and cpu limits in Kubernetes spec.