Deploy SvelteKit on Dokku in 8 Sort-of Easy Steps
Tags: dokku, svelte, devops
Published: 2021-09-22 11:33:38

*Disclaimer: I'm not being sponsored by anyone to write this, I'm just a really big fan of the tech in this stack* :heart:

Have you ever wanted&mdash;or needed&mdash;to host a webapp, but for one reason or another couldn't use the serverless offerings of Netlify, Vercel, Heroku, Deta et al? Those platforms are undoubtedly awesome, but on occasion, they just don't quite fit the bill.

Maybe it's because you can't deploy in your region and need to. Maybe the infrastructure your app needs is prohibitively expensive on a serverless stack. Maybe it's because of DBaaS co-location issues, or strict data compliance constraints. (side note, [Vercel](https://vercel.com) + [PlanetScale](https://planetscale.com) or [Supabase](https://supabase.com) = *chef kiss*)

Whatever your reasons, you find yourself in a situation where you need to explore self-hosting as an option.

## Enter [dokku](https://dokku.com/).

---

> "What the heck is dokku?? Like, [Heroku](https://heroku.com) or something?" 

In short: yes. It is very much like Heroku. Except for the awesome fact that it's your **very own DIY PaaS.**

Spin up a VM, install Dokku, create your app, `git push` your code and...it's done: your app is containerised, launched, stable, and ready to receive traffic, and you can install as many apps as your server can vertically scale for. (Of course, there's a *little* more to it than that :wink:)

There are even plugins for Postgres, Redis, Let's Encrypt and more, which helpfully abstract away the provisioning of those services, but not to the exclusion of retaining control if you want :+1:

The real beauty of it, for me, is that each time I make code changes, deployment becomes literally as simple as a `git push` and dokku will provision the new container and hot-swap the new version of the app with zero downtime.

So all the preamble said and done, if you're still with me, here are the 8(ish) sort-of easy steps to follow for a pain-free installation of SvelteKit on dokku.

---

## What we're going to do

We're going to deploy dokku onto a VM, and provision the SvelteKit demo app onto it.

## Prerequisites

* You'll need a private cloud VM with SSH access. There are so many options; two very popular ones are [Digital Ocean](https://digitalocean.com) and [Vultr](https://vultr.com).
Whatever box you choose, just make sure it's rocking Ubuntu 20.04, and it has at least 1GB RAM.
* You'll need a domain, and a DNS provider who lets you actually modify your records. Personally, I use [Netlify](https://netlify.com) for my DNS management.
* You need to be mostly unafraid of the linux commandline - dokku has no UI :wink:

Ready?

---

## Step 1: Provision your VM

Spin up your server and take note of the public IP address; we'll need that for step 2.

_I'm going to use `10.11.12.13` in the examples_

## Step 2: Set up your DNS records

_I'm going to use `example.io` for illustrative purposes: substitute your domain and IP as appropriate, and we do this step first because it takes a hot second for the records to propagate/cache._

In your DNS manager, create a wildcard A record.
```
  A   *.example.io  10.11.12.13
```
This points all subdomain traffic to our dokku server.

Additionally, if you want to also point the root of your domain to the same server (which you totally can), create a root A record as well.
```
  A   example.io  10.11.12.13
```

## Step 3: SSH into your VM

Let's get installing! We've got 2 packages we need to install: dokku and [pack](https://buildpacks.io/docs/tools/pack/). Pack is what dokku uses to convert your awesome app code into a container.

First up, go sudo if you aren't already.
```sh
sudo su
```

Install pack.
```sh
add-apt-repository ppa:cncf-buildpacks/pack-cli
apt-get update
apt-get install pack-cli
```

Now install dokku - at time of writing, v0.25.4 is the most recent stable version, but [check the docs](https://dokku.com/docs/getting-started/installation/#1-install-dokku) for a more recent version.
```sh
wget https://raw.githubusercontent.com/dokku/dokku/v0.25.4/bootstrap.sh
DOKKU_TAG=v0.25.4 bash bootstrap.sh
```

This will take a few minutes, be patient. Grab a coffee or do some squats or something.

## Step 4: Configure global configurables

First point of order is your root domain, which is easily set.
```sh
dokku domains:set-global example.io
```

Next we nudge dokku to use `main` as the default branch name rather than `master`.
```sh
dokku git:set --global deploy-branch main
```

Third is to configure the buildpack dokku will use to containerize your code. The awesome folks at [packeto.io](https://paketo.io) have put together a buildback that takes care of this for us.
```sh
dokku buildpacks:set-property --global stack paketobuildpacks/builder:base
```

Finally, access: ideally you want to not have to input a password every time you push from your repo, so if you've configured public-private keypair access, run this to give dokku access.
```sh
cat ~/.ssh/authorized_keys | dokku ssh-keys:add admin
```

## Step 5: Install plugins

There's one plugin we definitely want&mdash;Let's Encrypt, so let's install and configure that.
```sh
dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
dokku config:set --global DOKKU_LETSENCRYPT_EMAIL=mywebmasterinbox@gmailorother.com
dokku letsencrypt:cron-job --add
```
_(The email address you put here doesn't have to be affiliated with your domain in any way, it just has to be an address that you own.)_

Whether you need to install other plugins depends on what your apps need (check out the [full list](https://dokku.com/docs/community/plugins/#official-plugins)). For demonstration purposes, let's also install the Postgres plugin.
```sh
dokku plugin:install https://github.com/dokku/dokku-postgres.git
```

## Step 6: Prepare our app!

Before we can push our code to our server, we need a spot into which to push it!

```sh
dokku apps:create sveltekit-demo
```
(Again, name it whatever you wish, this is just for demo purposes)

Enable HTTPS via Let's Encrypt:
```sh
dokku letsencrypt:enable sveltekit-demo
```

For our demo app this next step is unnecessary, but it illustrates how to provision a postgres database, then link it to our app:
```sh
dokku postgres:create sveltekit-demo-pg
dokku postgres:link sveltekit-demo-pg sveltekit-demo
```
How easy is that! You'll see in the output, an ENV variable for the database URL has been generated - you can now references that variable in your code, and it will be hooked up in production :+1:

## Step 7: Prepare to Deploy

There are a couple of dokku-specific things we need to do to our codebase in your local machine, before we push it.

If you haven't already cloned the svelte demo, let's do that.
```sh
npm init svelte@next sveltekit-demo
# choose Demo, not Skeleton
# select the other options as you wish
cd sveltekit-demo
git init
git remote add dokku dokku@example.io:sveltekit-demo
git add .
git commit -m "Initial commit"
git branch -M main
```

Install `@sveltejs/adapter-node`.
```sh
npm i -D @sveltejs/adapter-node@next
```

Modify `svelte.config.js` to use the node adapter.
```diff
 import preprocess from 'svelte-preprocess';
+import adapter from '@sveltejs/adapter-node';

 const config = {
   preprocess: preprocess(),
   kit: {
+    adapter: adapter({
+      out: 'build'
+    }),
     // hydrate the <div id="svelte"> element in src/app.html
     target: '#svelte'
   }
 };
```

Modify the build script in `package.json`.

```diff
 // package.json
 {
   "scripts": {
-    "build": "svelte-kit build",
+    "build": "svelte-kit build && ln -s -f build/index.js server.js",
   }
 }
```
We create this symlink because packeto expects there to be a launch script called `server.js` in the project root. (There *is* a config that's supposed to let you change it, but that didn't work for me&mdash;this does though!)

Finally, create a file called `project.toml` - this is how we provide pack environment variables and other info specific to the build.

```toml
# project.toml
[build]
exclude = [
  "README.md"
  #, and/or another other files that are not relevant
  # to the build
]

[[build.env]]
name = "BP_NODE_RUN_SCRIPTS"
value = "build"

[[build.env]]
name = "NODE_ENV"
value = "dev"
```

The first environment variable, `BP_NODE_RUN_SCRIPTS`, tells pack to run `npm run-script build` after dependencies have been installed. You can run more than one command if you like, eg. `"lint,build"` - this var accepts a comma-delimited list of commands to run.

The second environment variable tells pack to set the build environment to "dev"&mdash;namely something other than "production". This is important, because without it, the dev dependencies don't get installed, which means the entire SvelteKit infrastructure is missing, which causes the build to fail.



## Step 8: Time to Push

Here goes nothing! Commit all your changes and push.
```sh
git add .
git commit -m "Make dokku compatible"
git push dokku
# if your trunk branch is called something other than main,
# do this instead
git push dokku BRANCH_NAME:main
```

Dokku will think for a moment, download the latest packer images from packeto, build your container, compile your svelte app, and launch it. :rocket: :tada: 

## Step 9 [BONUS]: Admire your work

You should now be able to navigate to `https://sveltekit-demo.[YOUR DOMAIN]` and see your handiwork!

---

## Official Docs
These are the ones I found helpful:

### Dokku
* [Getting started with Dokku](https://dokku.com/docs~v0.25.4/getting-started/installation/)
* [Deploying to Dokku](https://dokku.com/docs~v0.25.4/deployment/application-deployment/)
* [Git Deployment to Dokku](https://dokku.com/docs~v0.25.4/deployment/methods/git/)
* [CloudNative Buildpacks on Dokku](https://dokku.com/docs~v0.25.4/deployment/builders/cloud-native-buildpacks/)

### BuildPacks
* [Using Buildpack's project.toml](https://buildpacks.io/docs/app-developer-guide/using-project-descriptor/)

### Packeto
* [Packeto Node.js Primer](https://paketo.io/docs/howto/nodejs/)
* [Packeto Node.js Reference](https://paketo.io/docs/reference/nodejs-reference/)

---

## Final Thoughts

There are a few ways in which this process could be better (I only learned Dokku even existed yesterday lol), the most prominent of which is deployment directly from Github. Thankfully, Dokku have you covered [with a Github Action](https://dokku.com/docs/deployment/continuous-integration/github-actions/) there, and there's a Gitlab option too :+1:

---

The process should be reasonably adaptable to a React/Vue/Angular app - hopefully it helps someone out landing their first self-hosted PaaS!!

m@