
Table of Contents
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.
NITRO_PRESET=node-server and implement readiness probes to prevent routing traffic to uninitialized pods.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:
| Resource | Request | Limit | Rationale |
|---|---|---|---|
| CPU | 250m | 1000m | SSR is CPU-intensive; burst capacity prevents throttling during renders |
| Memory | 256Mi | 512Mi | Node.js heap + V8 overhead; prevents OOMKills on complex pages |
| Ephemeral Storage | 100Mi | 500Mi | Logs, 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.
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.
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.