Deploying Ruby on Rails 7 on Your Own Heroku, Using Dokku

November 18, 2021 · 11 min read

blog/hosting-rails-in-dokku

This tutorial shows how to deploy a Ruby on Rails 7 application with Sidekiq, Redis and Postgres to a VPS using Dokku, a container-based cloud Platform as a Service (PaaS). Many developers use Heroku to deploy, manage, and scale modern apps. You can quickly deploy a web application to Heroku by:

  1. Installing Heroku CLI
  2. Adding a GIT remote to your repository
  3. Using the git push command to deploy your application.

This process is very convenient, but it can get a bit expensive for hobby projects or if you need a large server. This tutorial shows you how to achieve a comfortable experience as a developer at a fraction of the cost. To achieve that goal, in my previous blog post, I showed how to install Dokku on a VPS server running Ubuntu 20.04 on Vultr. Using this link, you get $100 to use in the first 30 days.

Dokku is an open-source alternative to Heroku that tries to be as compatible with the way of working as Heroku. Dokku uses Docker under the hood, although as a developer, you don’t have to use it or containerize your applications; you can do it too. A few of the key advantages that I see in Dokku as a developer are:

  1. It is easy to host multiple web applications on a single server.
  2. You can create SSL certificates for your websites using let’s encrypt easily and add the renewal process as a cron to get the certificate auto-renewed when it’s closer to its expiry date.
  3. You can deploy web applications written in different languages or even different versions of the same language, like NodeJs, Python, Ruby, PHP, etc.
  4. Many plugins allow you to install additional software that your application might need, like databases such as Postgres, MongoDB, Elasticsearch, Redis, etc.
  5. Very easy to have Continuous Integration enabled from Github or Gitlab.
  6. Lastly, a great feature is that you can have zero downtime deployment very easily. As Dokku uses Docker to deploy your application, each time you trigger a new deployment, a new container is created, so it can wait until your new container is fully responsive before sending any traffic to it. All this in a transparent manner to you.

As a picture is worth a thousand words, here is a diagram of the final setup; in the previous post, I showed how to deploy Node.js; today, I will show how to deploy a simple Ruby on Rails 7 app.

dokku-final-setup

If you are convinced that this is an excellent Platform as a Service that you can use for your projects, keep reading.

Install Dokku in Ubuntu

I explained in more detail this process in my previous blog post, but after you connect to your Ubuntu Server, install the OS updates available by running:

 apt-get update && apt-get upgrade

Please connect to the server again, and install the apt-transport-https dependency to allow the package manager to install dependencies over HTTPS when it is available.

sudo apt-get -qq -y --no-install-recommends install apt-transport-https

Dokku is built on Docker and employs it to isolate applications. Install Docker with the command below.

wget -nv -O - https://get.docker.com/ | sh

The next step is to add the repository where Dokku is available and install Dokku and its dependencies.

wget -nv -O - https://packagecloud.io/dokku/dokku/gpgkey | apt-key add -
OS_ID="$(lsb_release -cs 2>/dev/null || echo "bionic")"
echo "bionic focal" | grep -q "$OS_ID" || OS_ID="bionic"
echo "deb https://packagecloud.io/dokku/dokku/ubuntu/ ${OS_ID} main" | sudo tee /etc/apt/sources.list.d/dokku.list
sudo apt-get update -qq >/dev/null
sudo apt-get -qq -y install dokku
sudo dokku plugin:install-dependencies --core

During the installation process, you will get some pop-ups asking for some configuration options.

  1. Keyfile for initial user: leave the default value /root/.ssh/id_rsa.pub as we will add an SSH key later.
  2. Enable the nginx-vhosts plugin? Dokku installs Nginx as the webserver and uses virtual hosts. We can have multiple websites or web apps deployed using different hostnames.
  3. Hostname or IP for the server: I prefer to use a domain here, so if I set it to example.com, each will be assigned a subdomain like [yourapp].example.com
  4. Enable vhost-based based deployments? Please enable it. So each app can be deployed based on its hostname, e.g. [yourapp].example.com.

At this point, you should have Dokku installed in your VPS.

Demo App to Deploy

I have created a basic Rails 7 alpha-2 app, and I have added sidekiq with a job to run every 5 minutes. And I’ve setup Postgres as database. You can find the codebase in Github.

Note: If you’re developing in MacOS and deploying to a Linux server, add the platform by running locally in your project folder bundle lock --add-platform x86_64-linux.

Deployment to Dokku

The steps to deploy this Rails app to Dokku are:

  1. Create Dokku app placeholder.
  2. Create Postgress database and link to the app, so that they can talk to each other.
  3. Create a Redis instance and link to the app.
  4. Add SSL Certificate from Let’s Encrypt.
  5. Add Github action to deploy on each commit to the main branch.

Let’s start with the first step; please connect via SSH to the server, create the app placeholder, and set the right environment variables running the command:

dokku apps:create rails7-sample
dokku config:set rails7-sample RAILS_ENV=production
dokku config:set rails7-sample RAILS_MASTER_KEY=your_rails_master_key

Create a Procfile in your project root with the following content:

web: bundle exec puma -C config/puma.rb

We use a Procfile to customize the run command, much like you would on Heroku or with a buildpack deployed app. The Procfile should contain one or more lines defining process types and associated commands.

Install PostgreSQL

dokku plugin:install https://github.com/dokku/dokku-postgres.git
dokku postgres:create rails7-sample-db
dokku postgres:link rails7-sample-db rails7-sample

It will set DATABASE_URL environment variable (same as Heroku Postgres).

If you want to run migrations automatically after each deployment; add the following line to the Procfile:

release: bundle exec rails db:migrate

If you prefer to run migrations manually instead, connect via SSH to the server and run the following command:

dokku run rails7-sample bundle exec rake db:migrate db:seed RAILS_ENV=production
Automatic backups to Amazon S3

You probably want to backup your data. Setting up an automated backup schedule to Amazon S3 is as simple as configuring AWS credentials:

dokku postgres:backup-auth rails7-sample-db AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY

And adding the following line to /etc/crontab, the database backup job is run once per day.

@daily root dokku postgres:backup rails7-sample-db your-s3-bucket-name

You can find more information about Postgres backups and export or import data in the documention from Github.

Install Redis for Sidekiq

Redis is a key-value store that stands out from others, like Memcached, in that it has built-in support for data structures like lists, sets, and hashes and can persist data to disk. It is pretty useful as both a cache store and as a full-fledged NoSQL data store. In Rails, Sidekiq uses Redis for background jobs and Action Cable for web-sockets. To install Redis and create an instance in your server, run the following commands:

dokku plugin:install https://github.com/dokku/dokku-redis.git redis
dokku redis:create rails7-sample-redis
dokku redis:link rails7-sample-redis rails7-sample

In this example, we are using Redis for Sidekq background workers, to define the worker process, add this to the Procfile:

worker: bundle exec sidekiq -C config/sidekiq.yml

Dokku will not spawn any non-web instances by default, so after deployment, we can enable the worker scaling by running ”dokku ps:scale rails7-sample web=1 worker=1 “.

You don’t need to do it every time. Dokku will remember the number of instances between builds.

Persistent Volumes for File Uploads

As I mentioned several times already, Dokku is based in Docker containers. That means that the storage in the container by default is temporary. Each time you deploy a new version of an application, Dokku creates a new container, and if you have file uploads or any other files generated at run time in your application are lost. We need to use Persistent Storage to have files persisted across multiple deployments.

If we want to upload files in our app to the folder /storage, we can create a persistent folder using the following command:

# creating storage for the app 'node-sample' in the VPS
mkdir -p  /var/lib/dokku/data/storage/rails7-sample-storage

# set the directory ownership.
chown -R 32767:32767 /var/lib/dokku/data/storage/rails7-sample-storage

# mount the directory into your container's /storage directory, relative to root
dokku storage:mount rails7-sample /var/lib/dokku/data/storage/rails7-sample-storage:/storage

The folder /var/lib/dokku/data/storage is created as part of the Dokku installation. It’s the recommended base folder to create the persistent folders that we can mount in our apps.

Once you have mounted persistent storage, you will also need to restart the application.

dokku ps:restart rails7-sample

After the app restarts, you can get a report about the app’s storage status using the storage:report command.

As you can see, this is a handy feature for user uploaded files or backups.

Add SSL Certificate and Enable Auto-Renew

These days it’s a recommended practice to use HTTPS, so to support SSL for your custom domains, there’s a plugin for Dokku that allows you to generate certificates from Let’s Encrypt. As they expire after three months, the plugin also supports creating a cron job to auto-renew it.

Connect back to your VPS with SSH and install Let’s Encrypt plugin:

sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
dokku config:set --global DOKKU_LETSENCRYPT_EMAIL=your-email@your.domain.com

After installing the Let’s Encrypt plugin, you can configure your domain and HTTPS for the app:

# set a custom domain that you own for your application
dokku domains:set rails7-sample myapp.com

# enable letsencrypt
dokku letsencrypt:enable rails7-sample

# enable auto-renewal
dokku letsencrypt:cron-job --add

Now you have enabled SSL for the Rails app, and the SSL certificate will automatically renew when it’s near its expiry date by the cron job we set in the last command above.

Remember to update the DNS for your domain: If you use a custom domain for your app, you need to set up the DNS correctly to point to the VPS IP before generating the SSL certificate. You might get an error like acme: error: 403 :: urn:ietf:params:acme:error:unauthorized, if you don’t do that.

If everything works, you should access your Rails app at the domain you defined, with HTTPS support.

Zero-Downtime Deployment

By default, Dokku will wait ten seconds after starting each container before assuming it is up and deploying your app and sending traffic to your new containers. Dokku will also wait a further sixty seconds after the deployment is complete before terminating old containers to give time for long-running connections to terminate. It’s good to have some sensible defaults, but it’s better to have certainty.

Dokku’s checks functionality allows checking whether an application can serve traffic or not more precisely.

To specify checks, add a CHECKS file to the root of your project directory. The content of the one I have added for the sample app is:

WAIT=8         # wait 8 seconds
TIMEOUT=5      # set timeout to 5 seconds
ATTEMPTS=6     # try 6 times
/check_deploy deploy_successful # Call /check_deploy and verify it replies 'deploy_successful'

Once the new container sends the ‘deploy_successful’ response to the /check_deploy request, Dokku understands that the container is ready to serve connections.

Github Action to Deploy Automatically on Git Push to the Main Branch

Until now, every deployment requires manually executing git push command on your host machine. I will show you how to automate this if you use Github as your source code repository, using a Github Action to deploy our code on changes automatically.

Create a .github/workflows/deploy.yml with the following content:

---
name: 'deploy'

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Cloning repo
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Push to dokku
        uses: dokku/github-action@master
        with:
          git_remote_url: ${{ secrets.DOKKU_GIT_REMOTE }}
          ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }}

This Github action executes when you push changes to the main branch. For Github to authenticate with Dokku in the VPS, we need to add the SSH_PRIVATE_KEY secret variable in Github.

As I created earlier a key, now I need to copy the private ssh key to the clipboard.

# on your local machine
cat ~/.ssh/dokku_rsa | pbcopy

Now in Github, go to your repository Settings, then select Secrets from the left menu. Click on New repository secret. Add your private key. Now each time that you push changes to Github main branch, those changes will be automatically deployed into your server.

Pedro Alonso

Software developer and consultant. I help companies build great products. I've worked with all kinds of companies. Contact me by email.

Get my new content delivered straight to your inbox. No spam, ever.

© 2021 Pedro Alonso