has_many :codes
Tips and walkthroughs on web technologies and digital life

Deploying an app with Dokku

Published  

I've been using Kubernetes for a few years now for deployments also for my personal projects, but not long ago I shut down my paid blogging platform DynaBlogger (which I will open source at some point) and I am now the only user of the app, so a Kubernetes cluster - however small - is now overkill.

For this reason I decided to switch to Dokku for this app, since now a single node (that's a limitation with Dokku) and fewer resources are more than enough for the traffic of this blog alone. Dokku is an interesting open source alternative to commercial PaaS (Platform As A Service) like Heroku, and it allows for the same nice user experience with git-push based deployments, but with your own servers; therefore you can use instances with any provider of your choice and potentially save some good cash compared to Heroku, especially considering that you can deploy multiple applications on a single server. Dokku also supports a number of plugins and even the same "buildpacks" that Heroku uses to deploy the apps. It also performs deployments without causing any downtime, which is awesome.

In this post I will show what are the most important commands to set up an application with Dokku. For more details of course I recommend your read the docs. This will be a quick how-to. 


Installation

Installing Dokku couldn't be easier, really. You just need to have a server already created, and run a bash script:

wget https://raw.githubusercontent.com/dokku/dokku/v0.26.8/bootstrap.sh

sudo DOKKU_TAG=v0.26.8 bash bootstrap.sh

At the moment of this writing the latest version is 0.26.8. The script will prepare the system by installing Nginx, Docker and deploying a bunch of utility containers. The installation only takes a few minutes. While the script sets up everything that Dokku needs, you still need to configure things like a firewall, SSH etc to harden the system. So don't skip those steps.


Global domain

You can configure multiple domains for each app, but you can also configure a wildcard domain name that will be used to generate default hostnames for your apps. All you need to do is configure a wildcard DNS record that points to the Dokku server. Then you need to add that global domain to Dokku's config:

dokku domains:add-global apps.mydomain.com
dokku domains:report --global

For now it is assumed that you run the "dokku" commands from the server. We'll see in a bit how you can run these commands directly from your local machine.


SSL/TLS certificates

Dokku can provision certificates for your apps automatically using Let's Encrypt, but this is not enabled out of the box. To enable it you need to install a plugin:

dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git


Setting up an application

The first step when setting up a new application is to create it:

dokku apps:create myapp

Linking services

Like I mentioned above, Dokku supports a number of plugins that allow you to add things like databases to your apps. For example you can install a plugin for Postgres:

dokku plugin:install https://github.com/dokku/dokku-postgres.git

Then you can create a Postgres instance with:

dokku postgres:create myapp-postgres

And "link" this Postgres instance with your application:

dokku postgres:link myapp-postgres myapp

This last command will set up an environment variable for your app called DATABASE_URL which contains the URL of the database. All your app needs to do is to connect to the database using the URL in that env variable.

You will very likely want to schedule backups for your database, and the Postgres plugin supports backups to s3 compatible storage. For this to work, you first need to configure the credentials required to access the bucket:

dokku postgres:backup-auth myapp-postgres <access key> <secret key> <region> v4 https://<endpoint>

I typically use Backblaze B2 for this since it's cheap but any s3 compatible object storage would do.

Then, to schedule for example a daily backup every morning at 5am:

dokku postgres:backup-schedule myapp-postgres "0 5 * * *" myapp-postgres-backup 

The schedule is in the usual cron format; the last argument is the name of the bucket, which you need to create.  You can test a backup manually with the following command:

dokku postgres:backup myapp-postgres myapp-postgres-backup 

You can also connect to the database with psql with the following command:

dokku postgres:connect myapp-postgres

Restores with the plugin are performed with pg_restore; if you need to restore an existing dump in plain SQL format, you can do that with this command:

dokku postgres:connect myapp-postgres < dump.sql

There are several other plugins you can install and use. For example I use Redis for background jobs and websockets and memcached for caching. Just search "dokku <service>" to find the relevant plugin. Installing a plugin, creating an instance for the service it supports and linking it to an application works the same way with every plugin.


App configuration

The configuration for an application is done via environment variables. You can use the following command to set environment variables:

dokku config myapp ENVIRONMENT_VARIABLE_NAME=value

You can specify multiple environment variables with a single command. By default this command will restart the application so that the new configuration is applied right away. If you don't want to restart the application, you can add the --no-restart flag.

You can view the whole configuration for your app at any time with:

dokku config:show myapp


Custom domains / issuing certificates

As mentioned before, Dokku assigns to each app a default hostname using the wildcard domain you have specified in the beginning, but you can also add one or more custom domains to your app:

dokku domains:add myapp myapp.com 

Then, in order to have Dokku provision a certificate for the app's domains with Let's Encrypt you need to run:

dokku letsencrypt:enable myapp

I also recommend you schedule automatic renewal of certificates:

dokku letsencrypt:cron-job --add

This last command is for all applications.


Buildpacks

By default, Dokku will attempt to detect what kind of app you are deploying, and use the relevant buildpacks to make that happen. However at times you may need to tweak which buildpacks it should use; for example, DynaBlogger is a Rails 7 app that uses esbuild and for the assets to be built correctly I had to revert the order of the two buildpacks Dokku uses, ruby and nodejs, so that nodejs is used first:

dokku buildpacks:clear myapp
dokku buildpacks:set myapp heroku/nodejs
dokku buildpacks:add myapp heroku/ruby


Configuring git-push deployments

As with Heroku, you can configure an origin with the git repository of your app so that a deployment with Dokku is triggered each time you push new changes. First, you need to add your public SSH key to the dokku user on the server:

echo <your public key>  | dokku ssh-keys:add admin

Then you can add the origin:

git remote add dokku dokku@<server ip or hostname>:myapp

I usually add two origins called staging and production for two environments, pointing to different instances of the app.

Now to trigger a deployment all you need is a git push:

git push dokku main

Of course you can easily automate this with a CI pipeline.


Custom Dockerfile

Dokku actually builds a Docker image for your app behind the scenes, and it works just fine with most apps. You can, however, use a custom Dockerfile if you want more control on how the image is built or if, for example, you need some dependencies that are not installed by any of the buildpacks available. In order to build the image with a custom Dockerfile, just add the Dockerfile to the repo and run:

dokku config:unset --no-restart myapp DOKKU_PROXY_PORT_MAP

This will switch from buildpack-based deployment to Dockerfile-based deployment.

I also recommend you run the following on the server to use Builtkit:

echo "export DOCKER_BUILDKIT=1" | sudo tee -a /etc/default/dokku
echo "export BUILDKIT_PROGRESS=plain" | sudo tee -a /etc/default/dokku


Running commands in the context of an application

You can exec into a running application and run a command in its context with the run Dokku command. For example:

dokku run myapp bash 

In this example, the bash shell will be executed if present.


Configuring multiple processes

Many applications require more than one process. For example, a web process for regular web traffic and a worker for background jobs. You can use a file named Procfile in the root of the repo to specify which processes you want to run. For example, for a typical Rails app I'd have this in the Procfile:

web: bundle exec puma -Cconfig/puma.rb
worker: bundle exec sidekiq



Scaling replicas

By default, Dokku starts a single replica only for the first process. If you want to change the number of replicas or ensure that one or more replicas are started also for the additional processes, you need the ps:scale command:

dokku ps:scale myapp web=2 worker=1



Tailing logs

You can view live logs for a deployment's process with this command:

dokku logs myapp -t -p web

The last argument is the name of the process whose logs you want to see.


Running dokku command from your local machine

You don't have to run dokku commands from the server. You can run them locally too. First you need to install the CLI - see this page. Then you need to configure SSH enabling tty connections, by adding the following to your ~/.ssh/config file:

Host apps
  HostName <server IP>
  User root
  RequestTTY force

Then configure an alias in your shell so that the command dokku actually translates to ssh -q apps dokku. Now you should be able to run most commands from your local machine (there are a few exceptions, but the commands you will use most often run just fine over SSH).


Wrapping up

This was a very quick how-to with the basic commands you need to quickly set up an application with Dokku. Please refer to the official documentation if you want to learn more. I really like that it's as easy to use as Heroku (after a small setup upfront) and can be much, much cheaper.

Comments

© Vito Botta