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
💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!