Dockerize a SvelteKit Application

Khimananda Oli 7 min read Programming and Languages
Dockerize a SvelteKit Application

By Khimananda Oli | Last reviewed: August 2026

To properly Dockerize a SvelteKit Application, you must move beyond basic tutorials and implement multi-stage builds that separate build dependencies from your runtime environment. This approach is critical for reducing attack surface and image size, especially when deploying to Kubernetes or cloud-native platforms where security and efficiency are paramount. If you are new to container fundamentals, start with my guide on containerizing applications from scratch before tackling SvelteKit’s specific SSR requirements.

Stage 1: Buildnode:22-alpinenpm ci --only=productionnpm run buildStage 2: Runtimenode:22-alpine (minimal)COPY /build + /package.jsonUSER node:node (non-root)Output Image< 150MB Final SizeEXPOSE 3000CMD ["node", "index.js"]
Multi-stage build architecture required to securely Dockerize a SvelteKit Application

How do you configure adapter-node to Dockerize a SvelteKit Application?

SvelteKit is an unopinionated framework, meaning it does not ship with a built-in HTTP server for production. Before you can Dockerize a SvelteKit Application, you must select an adapter that generates a standalone Node.js server. For most containerized deployments, @sveltejs/adapter-node is the correct choice because it outputs a self-contained directory with an entry point suitable for Docker.

Installing and configuring the adapter

First, install the adapter as a dev dependency. Do not install it as a production dependency, as it is only needed during the build phase:

npm install -D @sveltejs/adapter-node

Update your svelte.config.js to use the adapter. A common mistake is leaving the default auto-adapter, which may produce output incompatible with standard Node.js containers:

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: false,
            polyfill: true
        })
    }
};

export default config;

The out: 'build' parameter specifies where the compiled server assets will be placed. This path must match the COPY instructions in your Dockerfile exactly. When you run npm run build, SvelteKit generates a build/ directory containing index.js, handler files, and pruned production dependencies.

What is the optimal multi-stage Dockerfile to Dockerize a SvelteKit Application?

A single-stage Dockerfile that includes devDependencies, source code, and build tools creates bloated, insecure images. To properly Dockerize a SvelteKit Application, use a multi-stage build that isolates the compilation environment from the runtime. The following Dockerfile is battle-tested across AWS EKS and GKE clusters in 2026:

# Stage 1: Build
FROM node:22-alpine AS builder
WORKDIR /app

# Copy package files first for better layer caching
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts

# Copy source and build
COPY . .
RUN npm run build

# Prune devDependencies after build
RUN npm ci --omit=dev --ignore-scripts && \
    npm cache clean --force

# Stage 2: Production Runtime
FROM node:22-alpine AS runner
WORKDIR /app

# Security: Run as non-root user
RUN addgroup --system --gid 1001 nodejs && \
    adduser --system --uid 1001 sveltekit

# Copy only production artifacts
COPY --from=builder --chown=sveltekit:nodejs /app/build ./build
COPY --from=builder --chown=sveltekit:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=sveltekit:nodejs /app/package.json ./

# Set environment variables
ENV NODE_ENV=production
ENV PORT=3000
ENV HOST=0.0.0.0

USER sveltekit
EXPOSE 3000

# Health check for orchestrators
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget --no-verbose --tries=1 --spider http://localhost:3000/ || exit 1

CMD ["node", "build/index.js"]

Why this structure matters for compliance

In SOC 2 and ISO 27001 audits, container image hygiene is frequently scrutinized. This Dockerfile addresses three key control areas:

  • Least privilege: The USER sveltekit directive ensures the process cannot modify system files or escalate privileges if compromised.
  • Minimal attack surface: Alpine Linux reduces CVE exposure compared to Debian-based images, and excluding devDependencies eliminates unnecessary binaries like compilers and test frameworks.
  • Reproducibility: Using npm ci instead of npm install guarantees deterministic installs from your lockfile, preventing supply chain drift between environments.

If you need deeper context on securing container secrets during CI/CD, review Kubernetes secrets management done right to avoid baking credentials into your image layers.

ClientBrowser / APIContainer BoundaryNode.js RuntimeSvelteKit HandlerStatic AssetsExternalDB / Cache / APIPort 3000 • Non-root User • Alpine Base
Request flow inside a containerized SvelteKit application using adapter-node runtime

How does adapter-node compare to adapter-static for containerized SvelteKit?

Choosing the wrong adapter is the most frequent reason teams fail to successfully Dockerize a SvelteKit Application. While adapter-static produces pure HTML/CSS/JS files ideal for CDN hosting, it cannot handle server-side rendering (SSR), API routes, or form actions. Use this comparison to decide:

Criteriaadapter-nodeadapter-static
Server-Side Rendering✅ Full SSR support❌ Pre-rendered only
API Routes / Endpoints✅ Native support❌ Requires external backend
Docker RuntimeNode.js process requiredNginx / Caddy / S3 only
Image Size (Alpine)~120–150 MB~25–40 MB (nginx-alpine)
Dynamic Environment Variables✅ Runtime injection❌ Build-time only
Best ForSSR apps, auth, dynamic contentMarketing sites, docs, blogs

If your SvelteKit app uses +server.ts endpoints, server-side load functions, or form actions, you must use adapter-node. Attempting to force these features into a static adapter leads to broken functionality at runtime. For teams managing multiple frontend architectures, understanding these trade-offs prevents costly rework later. See microservices vs monolith trade-offs for broader architectural decision frameworks.

What are common pitfalls when you Dockerize a SvelteKit Application?

Even with a correct Dockerfile, subtle misconfigurations cause production failures. These are the issues I encounter most often during infrastructure reviews:

  1. Missing HOST binding: By default, SvelteKit’s adapter-node binds to localhost. Inside a container, this makes the app unreachable from outside. Always set HOST=0.0.0.0 via environment variable or adapter config.
  2. Incorrect file ownership: Forgetting --chown=sveltekit:nodejs in COPY commands causes permission errors when running as non-root. The container will crash immediately with EACCES errors.
  3. Dev dependencies in production: Running npm install instead of npm ci --omit=dev in the runtime stage bloats the image by 200–400 MB and introduces unnecessary vulnerabilities.
  4. Ignoring health checks: Without a HEALTHCHECK directive, Kubernetes and ECS cannot detect hung processes. Always include a lightweight endpoint probe.
  5. Hardcoded ports: Never hardcode port numbers in your application code. Use the PORT environment variable so orchestrators can assign dynamic ports.

Validating your container locally

Before pushing to a registry, validate the image behaves correctly:

# Build the image
docker build -t sveltekit-app:test .

# Run with explicit env vars
docker run -p 3000:3000 \
  -e NODE_ENV=production \
  -e HOST=0.0.0.0 \
  sveltekit-app:test

# Verify non-root execution
docker exec $(docker ps -q --filter ancestor=sveltekit-app:test) whoami
# Expected output: sveltekit

# Check image size
docker images sveltekit-app:test
# Target: under 150MB for alpine-based builds

If the whoami command returns root, your USER directive is misconfigured. If the image exceeds 200 MB, you likely included devDependencies or used a full Debian base unnecessarily.

❌ Naive Single-StageOS Layer (Alpine): 5 MBNode.js Runtime: 45 MBALL Dependencies (dev+prod): 350 MBSource Code + Build Artifacts: 80 MBBuild Tools (gcc, python, make): 120 MBTotal: ~600 MBRoot user • High CVE count • Slow deploy✅ Multi-Stage OptimizedOS Layer (Alpine): 5 MBNode.js Runtime: 45 MBProduction Dependencies Only: 60 MBCompiled Output (/build): 25 MB(No build tools, no source, no devDeps)Total: ~135 MBNon-root • Minimal CVEs • Fast scaling
Layer size comparison demonstrating why multi-stage builds are mandatory when you Dockerize a SvelteKit Application

Deploy Your Dockerized SvelteKit Application Confidently

When you Dockerize a SvelteKit Application correctly, you gain predictable deployments, faster scaling, and audit-ready artifacts. The multi-stage pattern outlined here has proven reliable across dozens of production systems I’ve architected in 2026, from Nepal-based startups to global SaaS platforms. Remember: security and efficiency are not optional extras—they are foundational requirements for any containerized workload. If you need help validating your container strategy or preparing for a compliance audit, reach out directly to discuss your specific infrastructure needs.

Frequently Asked Questions

Use node:22-alpine as your base image in 2026. It provides a minimal footprint under 200MB while maintaining full compatibility with SvelteKit adapters and native module compilation requirements for production deployments.

SvelteKit bakes public variables at build time, so pass them as build args. Keep secrets as runtime environment variables only. Never embed sensitive credentials during the docker build phase or they persist permanently in image layers.

Use @sveltejs/adapter-node for standard Docker deployments. It outputs a standalone Node.js server compatible with container orchestration, unlike static adapters which require separate web servers like Nginx for serving pre-rendered assets.

Check that you ran npm run build before copying files. Verify the entrypoint points to build/index.js and that all required environment variables are present. Missing build artifacts cause immediate container crashes.

Implement multi-stage builds, use Alpine Linux, and copy only the build output plus node_modules production dependencies. Exclude source maps, dev dependencies, and test files to achieve final images under 150MB consistently.

No, adapter-node includes a production-ready HTTP server. Adding Nginx adds complexity without meaningful performance gains for most workloads. Only add a reverse proxy if you need advanced caching, SSL termination, or rate limiting.

Expose a dedicated /health endpoint returning 200 OK. Configure Docker HEALTHCHECK with curl against this path. Set appropriate intervals and timeouts to prevent false positives during cold starts or garbage collection pauses.

Yes, pnpm reduces layer size through content-addressable storage. Use corepack enable in your Dockerfile and copy pnpm-lock.yaml. Run pnpm install --frozen-lockfile --prod to ensure deterministic, minimal dependency installation.

Let SvelteKit handle TypeScript transpilation during npm run build. Do not install typescript as a production dependency. The compiled JavaScript output in the build directory requires no runtime TypeScript tooling or configuration.

Expose port 3000 by default, matching adapter-node conventions. Override via PORT environment variable at runtime. Avoid hardcoding ports in Dockerfiles to maintain flexibility across different hosting environments and orchestrators.

Mount a persistent volume to the upload directory since containers are ephemeral. Configure SvelteKit body parser limits appropriately. For production, offload uploads to object storage like S3 rather than relying on local container filesystem.

No, always create a non-root node user in your Dockerfile. Switch to this user before CMD using USER directive. Running as root violates security best practices and may be blocked by Kubernetes pod security standards.

Add RUN echo statements between stages to verify file presence. Build without cache using --no-cache flag. Check builder stage logs separately from runtime stage. Validate each COPY instruction references correct paths relative to WORKDIR.

Prefer native npm run dev for faster HMR feedback loops. Reserve Docker Compose for integration testing with databases or external services. Development containers add latency that slows the tight edit-refresh cycle SvelteKit developers expect.

Update package.json and lockfile locally first, then rebuild the image. Never run npm update inside running containers. Pin exact versions in production to prevent unexpected breaking changes during routine maintenance or scaling events.