Run Nuxt on Kubernetes

Khimananda Oli 9 min read Programming and Languages
Run Nuxt on Kubernetes

By Khimananda Oli | Last reviewed: August 2026

Deploying server-side rendered applications requires a different operational mindset than static sites. When you run Nuxt on Kubernetes, you are managing a long-running Node.js process that must handle hydration, API proxying, and stateful rendering under variable load. Many teams struggle because they treat Nuxt like a simple SPA, resulting in memory leaks and cold-start latency during traffic spikes. This guide provides the exact configuration patterns I use in production to ensure stability, security, and cost efficiency for SSR workloads.

Ingress ControllerClusterIP ServiceNuxt Pod (SSR)Node.js + NitroNuxt Pod (SSR)Node.js + NitroNuxt Pod (SSR)Node.js + NitroConfigMap / SecretEnv Vars & Keys
High-level architecture when you run Nuxt on Kubernetes: Ingress routes to a ClusterIP service that load-balances across multiple SSR pods injected with configuration.

How do you containerize Nuxt for Kubernetes correctly?

The most common failure point when teams attempt to run Nuxt on Kubernetes is an improperly configured container image. You cannot simply copy your local .output directory into an Alpine image and expect production-grade performance. The key lies in understanding the Nitro engine's deployment presets and leveraging multi-stage builds to minimize attack surface and image size.

Selecting the right Nitro preset

Nuxt 3 uses Nitro as its underlying server engine. For Kubernetes, you must explicitly set the preset to node-server. Do not use the default node preset or platform-specific presets like vercel or cloudflare unless you are actually targeting those environments. The node-server preset produces a standalone entry point optimized for long-running Node.js processes, which is exactly what a Kubernetes pod is.

# nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    preset: 'node-server',
    // Enable compression at the app level if no service mesh
    compressPublicAssets: true,
    // Critical for container health checks
    routeRules: {
      '/healthz': { prerender: false }
    }
  }
})

Optimized multi-stage Dockerfile

Your Dockerfile should separate dependencies, build artifacts, and runtime. This reduces the final image to typically under 150MB and eliminates build tools from the production container. Always pin your Node.js version to an LTS release and use npm ci for deterministic installs.

# Stage 1: Dependencies
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

# Stage 2: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY . .
COPY --from=deps /app/node_modules ./node_modules
ENV NITRO_PRESET=node-server
RUN npm run build

# Stage 3: Production Runtime
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
# Create non-root user for security compliance
RUN addgroup --system --gid 1001 nodejs && \
    adduser --system --uid 1001 nuxt
COPY --from=builder /app/.output ./.output
USER nuxt
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]

A critical detail often missed is setting NODE_ENV=production in the runtime stage. Without this, Node.js may load development-only code paths, increasing memory usage by 30-50% and exposing debug endpoints. Also note the non-root user; running containers as root violates SOC 2 and ISO 27001 controls and creates unnecessary risk if a container escape occurs.

What Kubernetes resources are required to run Nuxt reliably?

Once you have a proper container image, you need to define Kubernetes manifests that reflect the actual resource behavior of a Nuxt SSR application. Unlike static file servers, Nuxt performs server-side rendering on every request that isn't cached, making it CPU-bound during traffic spikes and memory-sensitive during hydration.

Resource requests and limits

Setting appropriate resource requests and limits is non-negotiable for SSR workloads. Under-provisioning causes OOMKills during rendering; over-provisioning wastes budget. Based on production telemetry across multiple client projects, these baselines work for most Nuxt 3 applications:

ResourceRequestLimitRationale
CPU250m1000mSSR is CPU-intensive; burst capacity prevents throttling during renders
Memory256Mi512MiNode.js heap + V8 overhead; prevents OOMKills on complex pages
Ephemeral Storage100Mi500MiLogs, temp files, and npm cache if applicable

Always set limits equal to or higher than requests. For Nuxt specifically, avoid setting memory limits below 512Mi unless you've profiled your application extensively. The V8 garbage collector needs headroom, and hitting the limit triggers aggressive GC cycles that destroy response latency.

Health probes tuned for SSR

Nuxt applications take longer to initialize than simple Express servers due to route manifest generation and middleware setup. Configure your probes to account for this:

livenessProbe:
  httpGet:
    path: /healthz
    port: 3000
  initialDelaySeconds: 15
  periodSeconds: 20
  failureThreshold: 3
readinessProbe:
  httpGet:
    path: /healthz
    port: 3000
  initialDelaySeconds: 5
  periodSeconds: 10
  failureThreshold: 2
startupProbe:
  httpGet:
    path: /healthz
    port: 3000
  failureThreshold: 30
  periodSeconds: 2

The startup probe is essential. It gives Nuxt up to 60 seconds to initialize before the liveness probe kicks in. Without it, slow cold starts will trigger restart loops, creating the dreaded CrashLoopBackOff that plagues many Kubernetes debugging sessions.

Git Pushmain branchBuild ImageMulti-stageSecurity ScanTrivy / SLSAPush RegistryECR / GHCRHelm UpgradeArgoCD / Flux
Recommended CI/CD pipeline when you run Nuxt on Kubernetes: automated builds, security scanning, registry push, and GitOps-driven deployment.

How do you manage configuration and secrets for Nuxt in Kubernetes?

Nuxt expects environment variables at runtime for features like API base URLs, feature flags, and third-party keys. In Kubernetes, you must decouple these from your container image to maintain the same artifact across staging and production. This is where ConfigMaps and Secrets come in, but there are nuances specific to SSR frameworks.

Runtime vs build-time variables

Nuxt distinguishes between build-time and runtime configuration. Variables prefixed with NUXT_PUBLIC_ are embedded in the client bundle at build time and cannot be changed per-environment without rebuilding. True runtime variables must be accessed via useRuntimeConfig() in server routes and are injected via environment variables at pod startup.

# Helm values.yaml snippet
env:
  - name: NUXT_API_BASE
    valueFrom:
      configMapKeyRef:
        name: nuxt-config
        key: api-base
  - name: NUXT_SESSION_SECRET
    valueFrom:
      secretKeyRef:
        name: nuxt-secrets
        key: session-secret
  # Public vars MUST be baked at build time or set here
  - name: NUXT_PUBLIC_GA_ID
    value: "G-XXXXXXXXXX"

For sensitive values like database credentials or API keys, never use plain environment variables in your manifest. Use external secrets operators or sealed secrets to avoid committing encrypted values to Git. If you're operating under SOC 2 or ISO 27001 compliance, audit trails for secret access are mandatory, and native Kubernetes Secrets don't provide sufficient logging.

Handling public runtime config

A frequent pain point is that NUXT_PUBLIC_* variables are serialized into the JavaScript bundle during nuxt build. If you need different public values per environment, you have two options: rebuild per environment (breaks immutable infrastructure principles) or use a runtime substitution layer. The latter involves serving the public config via an API endpoint or injecting it into the HTML shell at request time using server middleware. This adds complexity but preserves true artifact immutability.

How do you scale and optimize Nuxt performance on Kubernetes?

Scaling SSR applications differs fundamentally from scaling APIs or static sites. Each Nuxt pod handles a limited number of concurrent renders before response times degrade. Understanding this constraint is key to configuring autoscaling that actually works.

Horizontal Pod Autoscaler configuration

For Nuxt, CPU-based autoscaling generally outperforms memory-based scaling because rendering is CPU-bound. Memory usage tends to be stable once the V8 heap warms up, while CPU spikes correlate directly with render concurrency. Target 60-70% CPU utilization to leave headroom for traffic bursts:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: nuxt-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: nuxt-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 65
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
      - type: Pods
        value: 2
        periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300

Note the asymmetric stabilization windows. Scale-up happens quickly (30s) to catch traffic spikes, while scale-down waits 5 minutes to prevent flapping. This is critical for SSR apps where cold starts are expensive. For deeper autoscaling strategies including custom metrics, see my guide on HPA configuration patterns.

Caching strategies to reduce render load

The best way to scale Nuxt is to avoid rendering altogether. Implement route-level caching via Nitro's routeRules or an external CDN. For dynamic content, consider stale-while-revalidate patterns that serve cached responses immediately while revalidating in the background. This can reduce your required pod count by 60-80% for content-heavy sites.

SSR Mode (node-server)Pod 1Pod 2Pod NDynamic rendering • HPA enabled • Higher costBest for: Personalized / Real-time contentStatic Mode (static)CDN / Object StoragePre-rendered HTML • No pods needed • Minimal costBest for: Docs / Marketing / Low-change sitesHybrid Mode (Recommended for Most Teams)Static Pages → CDNDynamic Routes → SSR PodsAPI Routes → Edge FunctionsUse Nitro routeRules to mix prerender + SSR per path
Deployment model comparison when you run Nuxt on Kubernetes: pure SSR, pure static, and hybrid approaches with trade-offs for cost and dynamism.

When should you choose hybrid rendering over pure SSR?

Not every route in your Nuxt application needs server-side rendering. Nitro's hybrid rendering allows you to prerender static pages at build time while keeping dynamic routes as SSR. This dramatically reduces the compute footprint when you run Nuxt on Kubernetes.

Configure hybrid rendering in your nuxt.config.ts:

export default defineNuxtConfig({
  routeRules: {
    // Static pages - prerendered at build
    '/': { prerender: true },
    '/about': { prerender: true },
    '/blog/': { swr: 3600 }, // Revalidate hourly
    
    // Dynamic pages - SSR at runtime  
    '/dashboard/': { ssr: true },
    '/api/**': { cors: true }
  }
})

This configuration means your Kubernetes pods only handle authenticated dashboard traffic and API calls. Marketing pages and blog posts are served from your CDN or ingress cache. In practice, this often lets teams reduce their minimum replica count from 3 to 1, cutting infrastructure costs by 60% while maintaining full SSR capabilities where they matter.

Running Nuxt on Kubernetes Successfully

Successfully operating Nuxt in Kubernetes comes down to respecting the unique characteristics of server-side rendering: CPU-intensive workloads, memory-sensitive runtimes, and the distinction between build-time and runtime configuration. Start with the multi-stage Dockerfile and node-server preset, set conservative resource limits with proper probes, and implement hybrid rendering to minimize unnecessary compute. Monitor your render latencies and error rates closely in the first weeks; SSR applications reveal infrastructure issues faster than any other workload type. If you need help architecting your Nuxt deployment or auditing your existing Kubernetes setup for compliance and performance, reach out to discuss your infrastructure.

Frequently Asked Questions

No, you can deploy static Nuxt builds using a simple Nginx container. However, SSR requires a Node.js runtime pod to handle dynamic requests and hydration.

Use node:22-alpine or node:24-alpine for production builds. Alpine reduces attack surface and image size significantly compared to Debian-based variants while maintaining compatibility with most Nuxt dependencies.

Point liveness probes to /api/_health or a custom endpoint returning 200 OK. Set initialDelaySeconds to 30 to allow Node.js startup and Nitro server initialization before checking.

Generally no. Nuxt SSR is stateless by default if you store session data externally in Redis or database. Avoid sticky sessions to enable proper horizontal pod autoscaling.

Inject runtime config via ConfigMaps or Secrets mounted as environment variables. Never bake secrets into Docker images; use NUXT_RUNTIME_CONFIG overrides at deployment time instead.

Start with 512Mi memory and 500m CPU requests. Monitor actual usage with Prometheus, then adjust based on p99 latency. Nuxt SSR is CPU-bound during rendering.

Yes, scale based on CPU utilization or custom metrics like request queue depth. Configure minReplicas to handle baseline traffic and maxReplicas to cap costs during spikes.

Serve .output/public through CDN or object storage. Configure Nuxt app.cdn.url to point assets externally, reducing pod bandwidth and improving global load times significantly.

NGINX Ingress Controller or Traefik both work well. Enable gzip compression and set proxy-buffer-size appropriately since SSR responses can be larger than typical API payloads.

Enable Nitro tracing and export traces to OpenTelemetry. Check pod resource throttling, cold starts, and external API latency. Profile server routes individually to find bottlenecks.

No. Build locally or in CI, copy only .output directory into the final image. Multi-stage builds keep production images under 200MB and eliminate dev dependencies entirely.

Use RollingUpdate strategy with maxSurge and maxUnavailable configured. Implement graceful shutdown hooks in Nitro to finish in-flight requests before pod termination completes.

None for typical deployments. Nuxt SSR is stateless. If using file-based caching or uploads, mount ephemeral storage or connect to S3-compatible object storage instead.

Store sensitive values in Kubernetes Secrets encrypted at rest. Mount as env vars or files. Rotate credentials regularly and restrict RBAC access to secret resources.

Kubernetes suits high-traffic apps needing predictable latency and custom infrastructure. Serverless fits sporadic traffic with lower ops overhead. Evaluate based on request volume and team expertise.