Next.js Kubernetes Deployment
Introduction
Deploying a Next.js application to Kubernetes combines the power of a modern React framework with a robust container orchestration system. Kubernetes (often abbreviated as K8s) provides a platform to automate deployment, scaling, and operations of application containers across clusters of hosts. This guide will walk you through the process of deploying your Next.js application to a Kubernetes cluster, making your application more scalable, reliable, and easier to manage.
By the end of this guide, you'll understand how to:
- Containerize your Next.js application with Docker
- Create necessary Kubernetes manifests
- Deploy your application to a Kubernetes cluster
- Configure networking and scaling
- Implement best practices for production deployments
Prerequisites
Before you start, make sure you have:
- A working Next.js application
- Docker installed on your machine
- kubectl command-line tool
- Access to a Kubernetes cluster (local like Minikube or remote)
- Basic understanding of containerization concepts
Step 1: Containerizing Your Next.js Application
The first step is to create a Docker image of your Next.js application.
Creating a Dockerfile
Create a Dockerfile
in the root of your Next.js project:
# Base image
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
WORKDIR /app
# Copy package files
COPY package.json package-lock.json* ./
RUN npm ci
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
# Uncomment the following line to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
# Create a non-root user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy the build output and public directory
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
# Set the correct permissions
RUN chown -R nextjs:nodejs /app
# Switch to the non-root user
USER nextjs
# Expose the port
EXPOSE 3000
# Run the application
CMD ["npm", "start"]
This Dockerfile uses multi-stage builds to:
- Install dependencies
- Build the Next.js application
- Create a smaller production image
Building and Testing the Docker Image
Build your Docker image:
docker build -t nextjs-k8s-app:latest .
Test if your image works correctly:
docker run -p 3000:3000 nextjs-k8s-app:latest
If everything is working, you should be able to access your Next.js application at http://localhost:3000.
Pushing the Image to a Container Registry
Before deploying to Kubernetes, push your image to a container registry:
# Tag the image for your registry (example with Docker Hub)
docker tag nextjs-k8s-app:latest yourusername/nextjs-k8s-app:latest
# Push the image
docker push yourusername/nextjs-k8s-app:latest
You can use Docker Hub, Google Container Registry (GCR), Amazon Elastic Container Registry (ECR), or any other container registry.
Step 2: Creating Kubernetes Manifests
Next, create the necessary Kubernetes manifest files to describe how your application should be deployed.
Deployment Manifest
Create a file named deployment.yaml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nextjs-app
labels:
app: nextjs-app
spec:
replicas: 2
selector:
matchLabels:
app: nextjs-app
template:
metadata:
labels:
app: nextjs-app
spec:
containers:
- name: nextjs
image: yourusername/nextjs-k8s-app:latest
ports:
- containerPort: 3000
resources:
limits:
cpu: "0.5"
memory: "512Mi"
requests:
cpu: "0.2"
memory: "256Mi"
env:
- name: NODE_ENV
value: "production"
livenessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 15
periodSeconds: 5
This manifest defines a deployment that:
- Creates 2 replicas of your application
- Uses your Docker image
- Sets resource limits and requests
- Sets environment variables
- Configures health checks through liveness and readiness probes
Service Manifest
Create a file named service.yaml
:
apiVersion: v1
kind: Service
metadata:
name: nextjs-service
spec:
selector:
app: nextjs-app
ports:
- port: 80
targetPort: 3000
type: ClusterIP
This service exposes your Next.js application within the cluster.
Ingress Manifest (Optional)
If you want to expose your application to the internet, create an ingress.yaml
file:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nextjs-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: your-domain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nextjs-service
port:
number: 80
This Ingress resource routes external traffic to your service. You'll need an Ingress controller installed in your cluster for this to work.
Step 3: Deploying to Kubernetes
Now that you have your manifests ready, deploy your application to the Kubernetes cluster.
Applying the Manifests
# Apply the deployment
kubectl apply -f deployment.yaml
# Apply the service
kubectl apply -f service.yaml
# Apply the ingress (if created)
kubectl apply -f ingress.yaml
Verifying the Deployment
Check if your pods are running:
kubectl get pods
Expected output:
NAME READY STATUS RESTARTS AGE
nextjs-app-6c9f8b8b8b-2x2xn 1/1 Running 0 2m
nextjs-app-6c9f8b8b8b-8xchd 1/1 Running 0 2m
Check if your service is created:
kubectl get services
Expected output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 24h
nextjs-service ClusterIP 10.104.132.15 <none> 80/TCP 2m
If you created an Ingress resource:
kubectl get ingress
Expected output:
NAME CLASS HOSTS ADDRESS PORTS AGE
nextjs-ingress <none> your-domain.com 192.168.64.2 80 2m
Step 4: Scaling and Managing Your Deployment
One of the benefits of using Kubernetes is the ability to easily scale your application.
Scaling Replicas
To scale your application to 5 replicas:
kubectl scale deployment nextjs-app --replicas=5
Rolling Updates
When you need to update your application, build and push a new Docker image with a new tag:
docker build -t yourusername/nextjs-k8s-app:v2 .
docker push yourusername/nextjs-k8s-app:v2
Update your deployment to use the new image:
kubectl set image deployment/nextjs-app nextjs=yourusername/nextjs-k8s-app:v2
Kubernetes will perform a rolling update, gradually replacing the old pods with new ones without downtime.
Step 5: Advanced Configuration
Environment Variables and ConfigMaps
For environment variables that might change between environments, use ConfigMaps:
apiVersion: v1
kind: ConfigMap
metadata:
name: nextjs-config
data:
API_URL: "https://api.example.com"
FEATURE_FLAGS: "enable-new-ui=true,dark-mode=false"
Reference these in your deployment:
containers:
- name: nextjs
image: yourusername/nextjs-k8s-app:latest
envFrom:
- configMapRef:
name: nextjs-config
Secrets for Sensitive Data
For sensitive information like API keys:
apiVersion: v1
kind: Secret
metadata:
name: nextjs-secrets
type: Opaque
data:
API_KEY: BASE64_ENCODED_API_KEY
Use these secrets in your deployment:
containers:
- name: nextjs
image: yourusername/nextjs-k8s-app:latest
env:
- name: API_KEY
valueFrom:
secretKeyRef:
name: nextjs-secrets
key: API_KEY
Resource Quotas
Implement resource quotas to limit resource usage:
apiVersion: v1
kind: ResourceQuota
metadata:
name: nextjs-quota
spec:
hard:
requests.cpu: "1"
requests.memory: 1Gi
limits.cpu: "2"
limits.memory: 2Gi
Real-World Example: A Production-Ready Next.js Deployment
Let's put everything together in a real-world example for a production-ready Next.js deployment:
Complete Kubernetes Manifest Set
Here's a more complete example that combines best practices:
namespace.yaml:
apiVersion: v1
kind: Namespace
metadata:
name: nextjs-production
configmap.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: nextjs-config
namespace: nextjs-production
data:
NODE_ENV: "production"
API_BASE_URL: "https://api.yourcompany.com/v1"
NEXT_PUBLIC_ANALYTICS_ID: "UA-XXXXXXXX-1"
secrets.yaml (in practice, use a secrets management solution like HashiCorp Vault):
apiVersion: v1
kind: Secret
metadata:
name: nextjs-secrets
namespace: nextjs-production
type: Opaque
data:
DATABASE_URL: BASE64_ENCODED_URL
AUTH_SECRET: BASE64_ENCODED_SECRET
deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nextjs-app
namespace: nextjs-production
labels:
app: nextjs-app
environment: production
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: nextjs-app
template:
metadata:
labels:
app: nextjs-app
environment: production
spec:
containers:
- name: nextjs
image: yourusername/nextjs-k8s-app:v1.0.0
ports:
- containerPort: 3000
resources:
limits:
cpu: "1"
memory: "1Gi"
requests:
cpu: "0.5"
memory: "512Mi"
envFrom:
- configMapRef:
name: nextjs-config
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: nextjs-secrets
key: DATABASE_URL
- name: AUTH_SECRET
valueFrom:
secretKeyRef:
name: nextjs-secrets
key: AUTH_SECRET
livenessProbe:
httpGet:
path: /api/health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /api/health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
startupProbe:
httpGet:
path: /api/health
port: 3000
failureThreshold: 30
periodSeconds: 10
imagePullSecrets:
- name: regcred
service.yaml:
apiVersion: v1
kind: Service
metadata:
name: nextjs-service
namespace: nextjs-production
spec:
selector:
app: nextjs-app
ports:
- port: 80
targetPort: 3000
type: ClusterIP
ingress.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nextjs-ingress
namespace: nextjs-production
annotations:
kubernetes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
spec:
tls:
- hosts:
- www.yourapp.com
secretName: nextjs-tls-secret
rules:
- host: www.yourapp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nextjs-service
port:
number: 80
horizontal-pod-autoscaler.yaml:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: nextjs-hpa
namespace: nextjs-production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: nextjs-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
Deploy Everything
# Create namespace first
kubectl apply -f namespace.yaml
# Apply ConfigMap and Secrets
kubectl apply -f configmap.yaml
kubectl apply -f secrets.yaml
# Deploy application components
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f ingress.yaml
kubectl apply -f horizontal-pod-autoscaler.yaml
Troubleshooting Common Issues
Pod in Pending State
If your pod remains in a pending state:
kubectl describe pod <pod-name>
Common causes:
- Insufficient resources in the cluster
- PersistentVolumeClaim not bound
- Image pull errors
Service Not Accessible
If you can't access your service:
kubectl get service nextjs-service
kubectl describe service nextjs-service
Check:
- Whether pods are running and ready
- If service selectors match pod labels
- Network policies that might block access
Container Crashing
If your container is crashing:
kubectl logs <pod-name>
Common issues:
- Missing environment variables
- Database connection problems
- Out of memory errors
Summary
In this guide, we've covered the complete process of deploying a Next.js application to Kubernetes:
- Containerizing your Next.js application with Docker
- Creating Kubernetes manifests for deployment
- Setting up services and ingress for networking
- Configuring environment variables and secrets
- Implementing autoscaling and resource limits
- Deploying and troubleshooting your application
By following these steps, you can create a robust, scalable deployment for your Next.js application that can handle production traffic effectively.
Kubernetes provides a powerful platform for running your Next.js applications, enabling you to scale seamlessly, update without downtime, and maintain high availability.
Additional Resources
- Official Kubernetes Documentation
- Next.js Documentation
- Docker Documentation
- Kubernetes Patterns by Bilgin Ibryam and Roland Huß
- Kubernetes: Up and Running by Brendan Burns, Joe Beda, and Kelsey Hightower
Exercises
- Basic: Deploy a simple Next.js application to a local Kubernetes cluster (Minikube or Kind)
- Intermediate: Set up a CI/CD pipeline that automatically builds and deploys your Next.js application to Kubernetes
- Advanced: Implement a blue-green deployment strategy for your Next.js application using Kubernetes
- Expert: Set up monitoring and logging for your Next.js application in Kubernetes using tools like Prometheus and Grafana
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)