Run SvelteKit with Node in Docker

Date: 2022-11-16 | sveltekit | node | docker |

Overview

SvelteKit is my favorite frontend framework. I containerize most of my projects for ease of development and the ability to run anywhere.

In this post we'll walk through how to build and serve SvelteKit with Node in a Docker container.

Requirements

In order to follow along with this tutorial, we're going to assume you already have a few things installed / available to you:

  • SvelteKit app using node-adapter - SvelteKit is built to be platform agnostic, leveraging configurable adapters for platform compatibility. The node-adapter allows SvelteKit to build and run with Node.
  • Docker - In order to run Docker containers on a machine, it will need to have Docker installed.

Containerize SvelteKit with Node

A typical Dockerfile to build and serve SvelteKit on Node looks like this:

Dockerfile

FROM node:lts-slim as build

WORKDIR /app

COPY package*.json ./
RUN rm -rf node_modules
RUN rm -rf build
COPY . .
RUN npm install
RUN npm run build

FROM node:lts-slim as run

WORKDIR /app
COPY --from=build /app/package.json ./package.json
COPY --from=build /app/build ./build
RUN npm install --production

EXPOSE 8080
ENTRYPOINT [ "npm", "run", "start" ]

The package.json commands look like this:

package.json

"scripts": {
    "build": "vite build",
    "start": "export PORT=8080 && node ./build",
  },

This Dockerfile is split into two sections:

  • Build - Installs all necessary dependencies and builds the optimized app executable utilizing vite
  • Run - Copies the app executable into a new container and serve it via Node

Note: This is the same Dockerfile / package.json I use to build and serve all my SvelteKit sites (including this one). You can get your hands on a full-stack, fully-Dockerized SvelteKit project with CloudSeed.

FAQ

I think the Dockerfile itself is pretty straightforward but there are likely a lot of open questions about why some decisions were made. As such I'm going to use the rest of this post to cover some common questions I've received / I researched during my build.

Q: Why a multistage Dockerfile?

We can save a decent chunk of storage by going multistage. This is because most SvelteKit sites will have some dependencies that are needed for dev / building the site but not actually for running it. By going multistage we omit those dependencies in the final image.

Anecdata:

  • Converting my most recent SvelteKit site from single to multistage shaved off ~20% of the slim image size (125mb -> 105mb (20mb, 16%))

Q: Why node:slim vs alpine / regular?

slim and alpine provide much smaller image sizes than regular (~75% smaller). In most cases, there's no reason to have a full on OS running your image (this is what regular provides) so in most cases you should go with a slim or alpine.

When choosing between slim and alpine it becomes a harder tradeoff. slim is typically a bit heavier than alpine (~20%) but it uses more common libraries which leaves less performance pitfalls. Whether or not the extra image savings is worth the potential performance pitfalls will depend on your workflow but I tend to go with slim to maximize savings and minimize risk.

alpine performance pitfalls references:

Anecdata:

  • regular: 409 mb
  • slim: 105 mb
  • alpine: 92 mb

Q: Why lts vs latest?

This is a harder one. lts (long-term support) is pretty behind in Node versions when compared to latest (currently at lts = 16 vs latest = 19). This likely means that latest is going to have much faster, more updated software.

That said, I typically think building on lts versions of software is better for your projects as it provides a much more stable base with more community support and less breaking changes.

If you do not specifically need some feature from a later release, I'd recommend sticking with lts. It will get these features eventually and when it does they will be well tested.

Q: Serving with node vs vite preview?

So most SvelteKit example projects will ship with a serve script utilizing vite preview. That said vite preview is specifically not meant for serving production traffic. From the official documentation:

It is important to note that vite preview is intended for previewing the build locally and not meant as a production server.

To be fair - this is fine in most cases. There are plenty of examples of frameworks not meant for production being used in production for years at high scale companies with little fanfare.

But if we're trying to do it "right" then we'll follow best practices and just serve with node.

Anecdotally I haven't really seen much of a difference in my sites' performance but I also don't serve much traffic so ymmv.

More resources

Want more like this?

The best / easiest way to support my work is by subscribing for future updates and sharing with your network.