Run SvelteKit on Kubernetes

Khimananda Oli 8 min read Programming and Languages
Run SvelteKit on Kubernetes

By Khimananda Oli | Last reviewed: August 2026

You want to run SvelteKit on Kubernetes because your application has outgrown simple PaaS hosting or requires tighter integration with internal microservices. While SvelteKit is often praised for its static site generation, running it as a server-side rendered (SSR) Node.js application in a container orchestrator demands specific configuration to handle hydration, streaming responses, and graceful shutdowns correctly. This guide provides the exact patterns I use in production to deploy resilient SvelteKit workloads, avoiding common pitfalls like missing environment variables during build time or improper signal handling that causes dropped requests during scaling events.

SvelteKit on Kubernetes Runtime ArchitectureIngress ControllerClusterIP ServicePod (SvelteKit Node)Node.js Server (Port 3000)Hydrated SSR AssetsPod (Replica 2)Node.js Server (Port 3000)Hydrated SSR AssetsConfigMap / SecretDATABASE_URLSESSION_SECRET
High-level topology when you run SvelteKit on Kubernetes: Ingress routes traffic to a ClusterIP service, which balances load across Pods containing the Node.js adapter, with secrets injected at runtime.

How do you prepare a SvelteKit application for Kubernetes deployment?

Before writing any YAML, your application must be packaged correctly. SvelteKit defaults to an adapter-based build system, and for Kubernetes, @sveltejs/adapter-node is the standard choice. It outputs a self-contained Node.js application that includes the server handler, prerendered pages, and client assets. A common mistake is trying to run the development server or using adapter-static with a custom Express wrapper; this adds unnecessary complexity and fragility.

Configure the Node Adapter

In your svelte.config.js, ensure the adapter is configured to output to a predictable directory and handle environment variables correctly. For production clusters, disable prerendering for dynamic routes unless you have a specific caching strategy.

import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

/** @type {import('@sveltejs/kit').Config} */
const config = {
  preprocess: vitePreprocess(),
  kit: {
    adapter: adapter({
      out: 'build',
      precompress: true,
      envPrefix: 'PUBLIC_'
    })
  }
};

export default config;

The precompress: true option generates gzip and brotli versions of static assets. When you configure Nginx or an ingress controller later, serving these precompressed files reduces CPU overhead and latency significantly. Understanding resource limits early helps prevent OOM kills during the build phase, which often consumes more memory than the runtime itself.

Create a Production-Grade Multi-Stage Dockerfile

Your Docker image should be lean and secure. Use a multi-stage build to separate dependencies, compilation, and the final runtime. The following Dockerfile is optimized for SvelteKit 2.x and Node.js 20 LTS:

# Stage 1: Dependencies
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile --prod=false

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

# Stage 3: Production Runtime
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
ENV HOST=0.0.0.0

RUN addgroup --system --gid 1001 nodejs && \
    adduser --system --uid 1001 sveltekit

COPY --from=builder /app/build ./build
COPY --from=builder /app/package.json ./package.json
COPY --from=deps /app/node_modules ./node_modules

USER sveltekit
EXPOSE 3000
CMD ["node", "build/index.js"]

This pattern keeps the final image under 200MB typically. Crucially, we copy only the build directory and production node_modules. Never run containers as root in production; the USER sveltekit directive enforces least privilege, aligning with Kubernetes security best practices.

What Kubernetes manifests are required to run SvelteKit reliably?

With a solid image, you need manifests that respect Node.js behavior. SvelteKit’s Node adapter listens on port 3000 by default and supports graceful shutdown via SIGTERM, but only if configured correctly in your deployment spec.

Deployment with Health Checks

Liveness and readiness probes are non-negotiable. Without them, Kubernetes cannot distinguish between a busy server and a dead one. The Node adapter exposes a basic health endpoint, but you can also probe the root path if your app handles it efficiently.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sveltekit-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: sveltekit-app
  template:
    metadata:
      labels:
        app: sveltekit-app
    spec:
      containers:
      - name: sveltekit
        image: registry.example.com/sveltekit-app:v1.2.0
        ports:
        - containerPort: 3000
        envFrom:
        - configMapRef:
            name: sveltekit-config
        - secretRef:
            name: sveltekit-secrets
        resources:
          requests:
            cpu: 100m
            memory: 256Mi
          limits:
            cpu: 500m
            memory: 512Mi
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 10
          periodSeconds: 15
        readinessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5

Note the initialDelaySeconds. Node.js applications, especially those hydrating large component trees on first request, may take several seconds to warm up. Setting this too low causes restart loops. If you encounter issues here, consult my guide on how to debug CrashLoopBackOff effectively.

Pod Startup & Health Check SequenceKubeletSvelteKit ContainerService MeshStart ContainerLoad Modules & Bind Port 3000 (~3s)Readiness Probe GET /health200 OK → Add to EndpointsFirst User RequestSSR Render + Hydration PayloadStream ResponseLiveness Probe GET /health200 OK → Continue Running
Startup sequence when you run SvelteKit on Kubernetes: the kubelet waits for module loading before probing, preventing premature traffic routing to cold containers.

How should environment variables and secrets be managed for SvelteKit?

SvelteKit distinguishes between public (PUBLIC_*) and private environment variables. In a containerized environment, this distinction becomes critical. Public variables are embedded into the client bundle at build time, while private variables must be available at runtime for server-side operations.

  • Build-time variables: Pass PUBLIC_API_URL during the Docker build stage. These become immutable in the JavaScript bundle. Changing them requires a new image tag.
  • Runtime variables: Database credentials, API keys, and session secrets must be injected via Kubernetes Secrets or ConfigMaps. The Node adapter reads process.env at startup, so these are immediately available without rebuilding.
  • Never commit secrets: Use external secret managers like AWS Secrets Manager or HashiCorp Vault synced to Kubernetes. For local development, .env files are fine, but never let them touch your CI pipeline.

If you are managing complex configurations across environments, consider reading about Kubernetes secrets management done right to avoid base64-encoded plaintext in your Git repository. Proper secret handling is essential for compliance frameworks like SOC 2 and ISO 27001, especially when handling user data in Nepali fintech or e-commerce applications.

How do you configure Ingress and autoscaling for SvelteKit?

SvelteKit serves both HTML and static assets. Your ingress controller must handle this duality efficiently. While the Node adapter can serve static files, offloading this to Nginx or a CDN improves performance dramatically under load.

FeatureNode Adapter OnlyIngress + CDN Offload
Static Asset ServingNode.js thread (blocking)Nginx/Cloudflare (non-blocking)
Gzip/Brotli CompressionCPU intensive per requestPrecompressed or edge-cached
SSL TerminationRequires cert-manager in podHandled at ingress layer
DDoS ProtectionLimited to pod resourcesWAF rules at edge
Cache-Control HeadersManual header configEdge cache rules + origin headers

For autoscaling, SvelteKit is typically CPU-bound during SSR rendering. Configure Horizontal Pod Autoscaler (HPA) based on CPU utilization rather than memory. A target of 70% CPU utilization with a minimum of 2 replicas ensures smooth scaling during traffic spikes. Remember that each pod needs time to warm up; set scaleUp.stabilizationWindowSeconds to at least 60 to prevent flapping.

Run SvelteKit on Kubernetes: Final Checklist

Successfully operating SvelteKit in production requires attention to detail beyond basic deployment. Verify your setup against these criteria before going live:

  1. Image Security: Scan every build with Trivy or Grype. Ensure no high/critical CVEs exist in the base Node image or dependencies.
  2. Graceful Shutdown: Test pod termination manually. Send SIGTERM and verify active requests complete before the process exits. The Node adapter handles this, but custom middleware might not.
  3. Observability: Instrument your app with OpenTelemetry. SvelteKit hooks are ideal places to add tracing spans for server-side rendering and API calls.
  4. Resource Right-Sizing: Use VPA recommendations after 48 hours of runtime. Over-provisioning wastes money; under-provisioning causes latency.
  5. Backup Strategy: If your SvelteKit app manages state or uploads, ensure persistent volumes are backed up. Static sites don’t need this, but SSR apps often do.

When you run SvelteKit on Kubernetes with these patterns, you gain scalability without sacrificing developer experience. The framework’s simplicity combined with Kubernetes’ power creates a resilient platform for modern web applications. If your team needs help architecting this stack or auditing an existing deployment, reach out to discuss your infrastructure requirements. I help teams build systems that pass audits and survive peak traffic, whether you’re serving users in Kathmandu or globally.

Frequently Asked Questions

Use node:22-alpine as your production base image for running SvelteKit on Kubernetes in 2026. It includes only runtime dependencies, reducing attack surface and image size. Build with multi-stage Dockerfiles to compile assets separately, ensuring the final container contains only the built adapter-node output and necessary environment variables.

Use @sveltejs/adapter-node for Kubernetes deployments since it outputs a standalone Node.js server. Avoid adapter-static unless serving purely static assets via nginx. The Node adapter supports SSR, API routes, and streaming responses natively, making it compatible with Kubernetes ingress controllers, health checks, and horizontal pod autoscaling without additional proxy layers.

Expose a /health endpoint in your SvelteKit app returning 200 OK. Configure livenessProbe and readinessProbe in your deployment manifest pointing to this path on port 3000. Set initialDelaySeconds to 5 and periodSeconds to 10. This ensures Kubernetes restarts unresponsive pods and removes them from service endpoints during rolling updates or failures.

Yes, but only if your app has no server-side logic. Serve pre-rendered files using nginx-unprivileged:1.27-alpine. For hybrid apps requiring SSR or API routes, adapter-node is mandatory. Static-only deployments lose dynamic features like form actions and server load functions, limiting functionality despite lower resource usage on Kubernetes clusters.

Start with 128Mi memory request and 256Mi limit, plus 100m CPU request and 500m limit per pod. Monitor actual usage via kubectl top pods after load testing. SvelteKit with adapter-node typically consumes 80-150MB at idle. Adjust based on traffic patterns, avoiding OOMKills while preventing resource waste across your Kubernetes cluster nodes.

Store secrets in Kubernetes Secrets or external vaults like HashiCorp Vault, never in ConfigMaps. Inject sensitive values as environment variables at runtime using envFrom secretRef. Keep non-sensitive config like PUBLIC_API_URL in ConfigMaps. Never bake secrets into Docker images. Rotate credentials regularly and restrict RBAC access to secret resources within namespaces.

Common causes include missing environment variables, incorrect PORT configuration, or failed database connections. Check logs with kubectl logs . Ensure adapter-node listens on process.env.PORT || 3000. Verify all required env vars are mounted. Add proper error handling in hooks.server.ts to prevent unhandled promise rejections from terminating the Node process during initialization.

Yes, unless accessing via ClusterIP internally. Install NGINX Ingress Controller or Traefik to route external traffic to your SvelteKit service. Configure TLS termination at the ingress level. Set up path-based routing if hosting multiple apps. Without an ingress, you must use port-forwarding or LoadBalancer services, which lack SSL termination and hostname-based routing capabilities.

HPA scales SvelteKit pods based on CPU, memory, or custom metrics like requests-per-second. Define minReplicas and maxReplicas in your HPA manifest targeting 70% CPU utilization. SvelteKit handles stateless requests well, enabling linear scaling. Pair with cluster autoscaler for node-level elasticity. Test scaling behavior under load to avoid cold-start latency affecting user experience during traffic spikes.

Costs vary by provider and scale. A minimal three-node cluster runs $50-100 monthly on managed Kubernetes in 2026. SvelteKit pods consume few resources, so compute costs stay low until high traffic demands more replicas. Factor in ingress, storage, and monitoring expenses. Optimize with spot instances and right-sizing to reduce spend without sacrificing reliability.

Implement response caching in hooks.server.ts using Cache-Control headers or integrate Redis for distributed caching. Kubernetes pods are ephemeral, so in-memory caches reset on restart. Use external cache stores for consistent performance across replicas. Configure CDN caching at the ingress layer for public assets. Avoid caching personalized responses to prevent data leakage between users.

Prefer Server-Sent Events for unidirectional streaming since they work over HTTP/2 and integrate with existing ingress configurations. WebSockets require special ingress annotations for upgrade handling and may break behind some load balancers. SvelteKit supports SSE natively via +server.js endpoints. Both protocols scale horizontally, but SSE simplifies infrastructure setup and avoids connection persistence issues.

Enable OpenTelemetry tracing with @opentelemetry/auto-instrumentations-node to identify bottlenecks. Check p99 latency via Prometheus metrics. Profile database queries, external API calls, and serialization overhead. Use kubectl exec to inspect pod resource contention. Review garbage collection pauses in Node.js. Correlate traces with logs to pinpoint whether slowness originates in application code, network hops, or downstream dependencies.

Yes, when scoped appropriately. Deploy SvelteKit as a frontend-for-backend service handling SSR and API aggregation. Avoid splitting every page into separate microservices; instead, group related domains. Each SvelteKit instance remains lightweight and independently deployable. Use service mesh for inter-service communication. This balances developer velocity with operational complexity better than monolithic or overly fragmented architectures.

Use RollingUpdate strategy with maxSurge=1 and maxUnavailable=0. Implement graceful shutdown handlers to finish in-flight requests before terminating. Configure readiness probes to delay traffic until new pods are fully initialized. Pre-warm caches during startup if needed. Test deployment behavior with kubectl rollout status. This ensures users experience no errors or interruptions during version upgrades on Kubernetes.