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 feedback@compilenrun.com. I’d greatly appreciate your feedback! :)