Disclaimer: I'm not being sponsored by anyone to write this, I'm just a really big fan of the tech in this stack ❤️
Have you ever wanted—or needed—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 + PlanetScale or Supabase = chef kiss)
Whatever your reasons, you find yourself in a situation where you need to explore self-hosting as an option.
Enter dokku.
"What the heck is dokku?? Like, Heroku 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 😉)
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 👍
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 and Vultr. 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 for my DNS management.
- You need to be mostly unafraid of the linux commandline - dokku has no UI 😉
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. Pack is what dokku uses to convert your awesome app code into a container.
First up, go sudo if you aren't already.
sudo su
Install pack.
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 for a more recent version.
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.
dokku domains:set-global example.io
Next we nudge dokku to use main
as the default branch name rather than master
.
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 have put together a buildback that takes care of this for us.
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.
cat ~/.ssh/authorized_keys | dokku ssh-keys:add admin
Step 5: Install plugins
There's one plugin we definitely want—Let's Encrypt, so let's install and configure that.
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). For demonstration purposes, let's also install the Postgres plugin.
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!
dokku apps:create sveltekit-demo
(Again, name it whatever you wish, this is just for demo purposes)
Enable HTTPS via Let's Encrypt:
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:
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 👍
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.
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
.
npm i -D @sveltejs/adapter-node@next
Modify svelte.config.js
to use the node adapter.
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
.
// 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—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.
# 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"—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.
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. 🚀 🎉
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
- Deploying to Dokku
- Git Deployment to Dokku
- CloudNative Buildpacks on Dokku
BuildPacks
Packeto
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 there, and there's a Gitlab option too 👍
The process should be reasonably adaptable to a React/Vue/Angular app - hopefully it helps someone out landing their first self-hosted PaaS!!
m@
Top comments (1)
Hi @emdienn , it should be enough to add
"start": "node build"
into yourpackage.json
instead of creating symlink.