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.
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.
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.
dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
Setting up an application
dokku apps:create myapp
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.
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
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.
dokku buildpacks:clear myapp dokku buildpacks:set myapp heroku/nodejs dokku buildpacks:add myapp heroku/ruby
Configuring git-push deployments
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.
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
dokku run myapp bash
In this example, the bash shell will be executed if present.
Configuring multiple processes
web: bundle exec puma -Cconfig/puma.rb worker: bundle exec sidekiq
dokku ps:scale myapp web=2 worker=1
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
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).