How to Dockerize SvelteKit
Hey there! This guide will help you containerize your SvelteKit app, making it easier to deploy and manage. Important Note: this is SvelteKit, not Svelte!:) Setup If you don't have a SvelteKit project, you can create one by running: npx sv create my-svelte-app --template demo --types ts First, we need to set up SvelteKit to use the adapter-node. This adapter helps build your site for Node.js, which is crucial for containerization. Start by installing the adapter-node: npm i -D @sveltejs/adapter-node Once installed, open your svelte.config.js file and change the adapter to use adapter-node: // svelte.config.js - import adapter from '@sveltejs/adapter-auto'; + import adapter from '@sveltejs/adapter-node'; Now, let's create the Dockerfile. This file tells Docker how to build and run your SvelteKit application. Here's what the Dockerfile might look like: # Use a Node.js Alpine image for the builder stage FROM node:22-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build RUN npm prune --production # Use another Node.js Alpine image for the final stage FROM node:22-alpine WORKDIR /app COPY --from=builder /app/build build/ COPY --from=builder /app/node_modules node_modules/ COPY package.json . EXPOSE 3000 ENV NODE_ENV=production CMD [ "node", "build" ] Let's break down the Dockerfile: Builder Stage: We start with a Node.js Alpine image, set the working directory to /app, copy the package files, install dependencies, copy the rest of the source files, build the SvelteKit app, and prune dependencies to production-only. Final Stage: We start fresh with another Alpine Node.js image, copy over the built app, production node_modules, and package.json from the builder stage, expose port 3000, set the environment to production, and specify the command to run the app. To keep the Docker build context clean and speed up the build process, create a .dockerignore file in your project root: Dockerfile .dockerignore .git .gitignore .gitattributes README.md .npmrc .prettierrc .eslintrc.cjs .graphqlrc .editorconfig .svelte-kit .vscode node_modules build package **/.env Using a multi-stage build helps keep the final image small by discarding unnecessary files and tools after the build process. It also speeds up builds by leveraging Docker's layer caching. When using adapter-node, you should generally install all dependencies as devDependencies. This allows SvelteKit to bundle them into your app and discard any unused imports. Install new packages with the -D flag, like npm i -D lucide-svelte, whether they're for client-side or server-side use. However, if you encounter issues like __dirname is not defined during the build, you might need to install the dependency as a regular dependency. This means SvelteKit won't bundle it, and it will be loaded from node_modules at runtime. If all your dependencies are devDependencies, you can skip copying node_modules in the final stage of the Dockerfile, further reducing the image size. To build your Docker image, run the following command from the directory containing your Dockerfile: docker build -t my-sveltekit-app . Once the build is complete, you can run your containerized app with: docker run -p 3000:3000 my-sveltekit-app You can now access your SvelteKit app by navigating to http://localhost:3000 in your web browser. Handling environment variables in Docker is important. SvelteKit provides four ways to import environment variables, each with different implications for Docker: $env/dynamic/private: These are runtime variables that don't need to be defined at build time. You can set them in the Dockerfile, pass them when running the container, or use a .env file. $env/dynamic/public: Similar to dynamic private variables but exposed to the client. They should start with PUBLIC_. $env/static/private: These are build-time variables that get baked into your Docker image. Use build arguments in your Dockerfile and pass them during the build process. $env/static/public: Similar to static private variables but exposed to the client. Useful for static builds with adapter-static. When deploying your app, remember that different platforms handle build-time and runtime environment variables differently. Always refer to the platform's documentation. Lastly, when using form actions or other server-side features in Docker, you need to set the ORIGIN environment variable correctly to avoid cross-site POST form submission errors. For example: docker run -p 3000:3000 -e ORIGIN=http://localhost:3000 my-sveltekit-app In production, replace http://localhost:3000 with your actual domain. Production Tips Health Checks: Add a /health endpoint to your app and use Docker's HEALTHCHECK instruction to monitor container health: HEALTHCHECK --interval=30s --timeout=3s \ CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1 Memo
Hey there! This guide will help you containerize your SvelteKit app, making it easier to deploy and manage. Important Note: this is SvelteKit, not Svelte!:)
Setup
If you don't have a SvelteKit project, you can create one by running:
npx sv create my-svelte-app --template demo --types ts
First, we need to set up SvelteKit to use the adapter-node. This adapter helps build your site for Node.js, which is crucial for containerization.
Start by installing the adapter-node:
npm i -D @sveltejs/adapter-node
Once installed, open your svelte.config.js
file and change the adapter to use adapter-node:
// svelte.config.js
- import adapter from '@sveltejs/adapter-auto';
+ import adapter from '@sveltejs/adapter-node';
Now, let's create the Dockerfile. This file tells Docker how to build and run your SvelteKit application. Here's what the Dockerfile might look like:
# Use a Node.js Alpine image for the builder stage
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
RUN npm prune --production
# Use another Node.js Alpine image for the final stage
FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app/build build/
COPY --from=builder /app/node_modules node_modules/
COPY package.json .
EXPOSE 3000
ENV NODE_ENV=production
CMD [ "node", "build" ]
Let's break down the Dockerfile:
Builder Stage: We start with a Node.js Alpine image, set the working directory to
/app
, copy the package files, install dependencies, copy the rest of the source files, build the SvelteKit app, and prune dependencies to production-only.Final Stage: We start fresh with another Alpine Node.js image, copy over the built app, production node_modules, and package.json from the builder stage, expose port 3000, set the environment to production, and specify the command to run the app.
To keep the Docker build context clean and speed up the build process, create a .dockerignore
file in your project root:
Dockerfile
.dockerignore
.git
.gitignore
.gitattributes
README.md
.npmrc
.prettierrc
.eslintrc.cjs
.graphqlrc
.editorconfig
.svelte-kit
.vscode
node_modules
build
package
**/.env
Using a multi-stage build helps keep the final image small by discarding unnecessary files and tools after the build process. It also speeds up builds by leveraging Docker's layer caching.
When using adapter-node, you should generally install all dependencies as devDependencies. This allows SvelteKit to bundle them into your app and discard any unused imports. Install new packages with the -D
flag, like npm i -D lucide-svelte
, whether they're for client-side or server-side use.
However, if you encounter issues like __dirname is not defined
during the build, you might need to install the dependency as a regular dependency. This means SvelteKit won't bundle it, and it will be loaded from node_modules at runtime.
If all your dependencies are devDependencies, you can skip copying node_modules in the final stage of the Dockerfile, further reducing the image size.
To build your Docker image, run the following command from the directory containing your Dockerfile:
docker build -t my-sveltekit-app .
Once the build is complete, you can run your containerized app with:
docker run -p 3000:3000 my-sveltekit-app
You can now access your SvelteKit app by navigating to http://localhost:3000
in your web browser.
Handling environment variables in Docker is important. SvelteKit provides four ways to import environment variables, each with different implications for Docker:
$env/dynamic/private: These are runtime variables that don't need to be defined at build time. You can set them in the Dockerfile, pass them when running the container, or use a .env file.
$env/dynamic/public: Similar to dynamic private variables but exposed to the client. They should start with
PUBLIC_
.$env/static/private: These are build-time variables that get baked into your Docker image. Use build arguments in your Dockerfile and pass them during the build process.
$env/static/public: Similar to static private variables but exposed to the client. Useful for static builds with adapter-static.
When deploying your app, remember that different platforms handle build-time and runtime environment variables differently. Always refer to the platform's documentation.
Lastly, when using form actions or other server-side features in Docker, you need to set the ORIGIN
environment variable correctly to avoid cross-site POST form submission errors. For example:
docker run -p 3000:3000 -e ORIGIN=http://localhost:3000 my-sveltekit-app
In production, replace http://localhost:3000
with your actual domain.
Production Tips
-
Health Checks: Add a
/health
endpoint to your app and use Docker's HEALTHCHECK instruction to monitor container health:
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
- Memory Limits: Set Node.js memory limits to prevent container crashes:
docker run -p 3000:3000 -e NODE_OPTIONS="--max-old-space-size=512" my-sveltekit-app
- Security Scanning: Regularly scan your container for vulnerabilities:
docker scout quickview
Conclusion
That's it! You now have a dockerized SvelteKit application that's optimized for production. If you want to deploy your SvelteKit app, check out sliplane.io