Dockerizing SvelteKit: Reducing Image Size from 1GB to 100MB with Multi-stage Builds

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Why SvelteKit Needs a Standard Dockerization Process

SvelteKit is currently an incredibly powerful framework for modern web applications. However, unlike pure React or Vue (SPA) applications that only need to host static files, SvelteKit runs in Server-Side Rendering (SSR) mode by default. This means you need a real Node.js environment to run your server-side code.

The most common mistake developers make is using a naive Dockerfile. Simply starting FROM node and running npm install will result in an image weighing over 1GB. Oversized images slow down CI/CD processes, waste Registry storage, and cause containers to start sluggishly.

In practice, I’ve seen many projects suffer from library conflicts between Windows and Linux just because the entire node_modules folder was copied into Docker. Or worse, using adapter-auto leaves the container confused about which command to use to run the application upon deployment. Here is how I handle these pitfalls.

Step 1: Switch to Adapter-Node

By default, SvelteKit uses @sveltejs/adapter-auto. For stable operation on Docker or a VPS, switch to @sveltejs/adapter-node. It builds the application into a pure Node.js server, which is much easier to control.

Install the adapter now:

npm install -D @sveltejs/adapter-node

Next, update your svelte.config.js file:

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' })
	}
};

export default config;

Step 2: Clean Up with .dockerignore

Don’t let Docker waste time scanning thousands of junk files. Ignoring node_modules helps reduce build time from minutes to seconds. Create a .dockerignore file in your root directory:

node_modules
.svelte-kit
build
.env
.env.*
!.env.example
npm-debug.log
.git
.DS_Store

Step 3: Writing an Optimized Dockerfile (Multi-stage Build)

The Multi-stage Build technique is like using all sorts of knives and boards to cook in the kitchen, but only bringing the clean, finished dish to the dinner table. We use a full Image to build the app, then only take the results (artifacts) and place them into a super-lightweight alpine Image.

# Stage 1: Build stage
FROM node:20-alpine AS builder
WORKDIR /app
# Leverage Docker layer cache by copying package files first
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Run stage
FROM node:20-alpine AS runner
WORKDIR /app
# Only take what is strictly necessary
COPY --from=builder /app/build ./build
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/package-lock.json ./package-lock.json

# Install production dependencies only, skipping devDependencies
RUN npm ci --prod

ENV NODE_ENV=production
ENV PORT=3000
EXPOSE 3000

CMD ["node", "build"]

The result? Your image will shrink from 1GB to around 120-150MB. Using npm ci --prod helps eliminate bulky libraries like Vite or Svelte-check from the actual production environment.

Step 4: Nginx – The Essential Protection Layer

Even though Node.js can run on its own, placing Nginx in front is a wise choice. Nginx handles Gzip compression more efficiently and makes SSL configuration much easier. Create an nginx/default.conf file:

server {
    listen 80;
    server_name localhost;

    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml;

    location / {
        proxy_pass http://sveltekit_app:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

Step 5: Deployment with Docker Compose

To connect the application and Nginx, use Docker Compose. This is the simplest way to manage multiple containers simultaneously.

services:
  sveltekit_app:
    build: .
    container_name: sveltekit_container
    restart: always

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - sveltekit_app
    restart: always

Operation and Testing

Activate the system with the command: docker-compose up -d --build. Once the container is running, try typing docker stats. You’ll see a small surprise: a standard SvelteKit application typically consumes only about 50-70MB of RAM. This figure is extremely impressive compared to other heavy frameworks.

Hard-earned lesson: When deploying to cheap VPS instances (like the $5 DigitalOcean plan), always limit the RAM for your containers. This prevents a single container with a memory leak from crashing the entire server. Good luck with your SvelteKit Dockerization!

Share: