Sooner or later you’re going to want to deploy your Svelte app to a server. That’s cool, but how do you do that?

The simple answer is to use Docker(*) (realistically!) to create an image and then deploy it using AWS EKS or ECS or other container tech. But how? Here’s how.

You can use Vercel which has much more integration with Svelte, but when I tried to use it, it didn’t work with a vanilla site. I put a comment in Github under and they said they’d look into it. I’ll keep an eye on that.

Set the build parameters

Now let’s set some parameters for the build. Cut and paste these into your console. You should adjust the LATEST_TAG to the latest version of your app, and the REPO to the name of your repository. You’ll need to change the value for ACCOUNT and REGION to match your AWS account and region.

REPO="my-new-svelte-site"
ACCOUNT="123456478901"
REGION="eu-west-2"
REGISTRY="${ACCOUNT}.dkr.ecr.${REGION}.amazonaws.com"
LATEST_TAG='0.1.8'
IMAGE=${REGISTRY}/${REPO}

Create new repository for your image

I’m using the Amazon ECR in eu-west-2 (London) registry for my image, because it’s local to people in the UK and I’m all about AWS. There are others like Docker Hub which is free for public images and also very good.

Create a new repository for your image. Let’s call the new repo my-new-svelte-site. Do that in the AWS console, or use the AWS CLI like this.

aws ecr create-repository --repository-name ${REPO} --region ${REGION}

Building the image

Now let’s build the image. Here’s the Dockerfile which does the build in two stages so it’s as lean and as small as possible. I’m using pnpm as my package manager, but you can use npm or yarn if you prefer. I’m using the minideb image as my base image, because it’s small and has everything I need.

The working directory is /app and the app is built using vite which is a super fast build tool for Svelte. The fully built website will appear in the /app directory of the final image. I’m also using dumb-init as the entrypoint to the image, because it’s a good practice to use an init system in your Docker images and dumb-init is easy to use and very small.

pnpm

A word on pnpm. It’s miles faster than npm. Install using npm install -g pnpm or see the pnpm website for more installation options for Windows or Mac. Once installed, run pnpm install instead of npm install which will create the pnpm-lock.yaml file which will appear in the root of the project and you can use that to install the packages in the Dockerfile.

Place the following in the root of your project and name it Dockerfile. By ‘root of your project’, I mean the same directory as your package.json and pnpm-lock.yaml files, not the src directory.

Dockerfile

# ---> The build image
FROM bitnami/minideb:bullseye@sha256:a062b0f35fe88a6bdef3e72850d49be1ce829accfec81b4cd474d4d61cf82b16 AS builder
LABEL maintainer "Morgan Conlon <morgan@cloudguyinbroadstone.com>"
WORKDIR /app
SHELL ["/bin/bash", "-o", "pipefail", "-c"]

# install dumb-init and nodejs LTS
RUN apt-get update && \
    echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections && \
    apt-get install -y -qq --no-install-recommends \
    curl \
    dumb-init \
    npm && \
    curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && \
    apt-get install -y nodejs && \
    npm install -g pnpm && \
    rm -rf /var/lib/apt/lists/*

# copy package.json and pnpm-lock.yaml to /app then install the packages to create /app/node_modules
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

# copy the app source files to /app, using .dockerignore to filter everything but key files, then build the app
COPY . .

RUN npx vite build

# ---> The production image
FROM bitnami/minideb:bullseye@sha256:a062b0f35fe88a6bdef3e72850d49be1ce829accfec81b4cd474d4d61cf82b16
LABEL maintainer "Morgan Conlon <morgan@cloudguyinbroadstone.com>"
ENV NODE_ENV production
WORKDIR /app
SHELL ["/bin/bash", "-o", "pipefail", "-c"]

# copy nodejs and dumb-init from the builder image to /usr/bin
COPY --from=builder /usr/bin/node /usr/bin/dumb-init /usr/bin/

# create a node user with uid 1001
RUN groupadd node && useradd -rm -d /home/node -s /bin/bash -g root -G sudo -u 1001 node
USER node

# copy the node_modules and compiled app from the builder image to /app
COPY --chown=node:node --from=builder /app/ .

# set the port to 3000
EXPOSE 3000
CMD ["dumb-init", "node", "build"]

.dockerignore

This is an often overlooked file. It’s used to filter out files and directories from the build context so that they don’t get copied into the image. Here’s my typical .dockerignore file for a Svelte app. It goes in the same folder as the Dockerfile.

**/build
**/docker
**/Dockerfile*
**/.dockerignore
# .env
**/.eslintignore
**/.eslintrc.cjs
**/.git
**/.github
**/.gitignore
**/node_modules
node_modules
**/.npmrc
# package.json
**/playwright.config.js
# pnpm-lock.yaml
**/.prettierignore
**/.prettierrc
**/README.md
# src
# static
# svelte.config.js
/.svelte-kit
**/tests
# vite.config.js

Build the image

OK, let’s build.

docker build --network=host --pull -t ${IMAGE}:${LATEST_TAG} -f Dockerfile .

Run the website

The website will be at http://localhost:3000/ and can be started using the following command.

docker run -p3000:3000 --network=host -it ${IMAGE}:${LATEST_TAG}

Examine the image

if you need to examine the container, you can connect to it like this. All your code is in /app, compiled into JavaScript and CSS.

docker run -p3000:3000 --network=host -it ${IMAGE}:${LATEST_TAG} /bin/bash

Log into the registry

Authenticate your Docker client to the Amazon ECR registry to which you intend to push your image. Authentication tokens must be obtained for each registry used, and the tokens are valid for 12 hours.

aws ecr get-login-password --region ${REGION} | docker login --username AWS --password-stdin ${ACCOUNT}.dkr.ecr.${REGION}.amazonaws.com

Tag and push the image

So far so good. Let’s tag and push the image to the repo.

docker tag ${IMAGE}:${LATEST_TAG} ${IMAGE}:latest

..and now push.

docker push ${IMAGE}:${LATEST_TAG} ${IMAGE}:latest

Update the image tags

Finally, update the image tags in the repository to point to the latest image.

GET_IMAGES=$(aws ecr batch-get-image --repository-name ${REPO} \
--image-ids imageTag=${LATEST_TAG} \
--query 'images[].imageManifest' \
--output text)
aws ecr put-image --repository-name ${REPO} --image-tag latest --image-manifest "$GET_IMAGES"

and that’s it. Your Svelte app is now built and deployed to the repo using Docker.

Conclusion

Now you have a Svelte app in a container repo, you can host it on a service provider like AWS, Google Cloud, Azure, or Digital Ocean or Vercel. You can also host it on your own server using Docker. The choice is yours. I hope this article has been helpful. If you have any questions, please ask in the comments below. Thanks for reading.

Next month, I’ll be writing about how to deploy a Svelte app to AWS EKS using Terraform. Stay tuned for that.