Deploy Ruby on Rails 7.0 to Dokku micro PaaS
Reading time: 10 minutes
This tutorial was tested with Dokku version 0.34.0
Deploying a Ruby on Rails application to a Dokku instance is pretty forward. There are only a few things you have to consider. In this guide, I will tell you, what might be the things and how to solve them. I was missing a complete guide with explanations aligned to the deployment of a default Ruby on Rails application.
Motivation
I love the way Heroku solved deployment and management on their Platform as a Service (PaaS). What I don’t like is the overall slowness and the very limited amount of memory Heroku provides resulting in a very fast overload of an instance by even low increase of traffic. It may be related to low AWS performance or overload of the system hosting too many Heroku instances on an AWS EC2 system.
Another thing is the missing support of HTTP/2, see: SPDY and HTTP/2 are not supported at this time. There are applications, that are slower by several factors without HTTP/2. Also, Heroku supports Websockets as you can see here: Heroku Websocket, but it’s not a big of help for almost any website or application using HTTP/2 or newer.
As PaaS user, you may don’t want to see every detail of the system like CPUs, RAM, and even database details. But if things start to slow down or break at some point, it gets messy, because you or your team are the ones who are responsible for the running application, even if Heroku is proving the platform to work smoothly. For me, it failed several times and failed hard.
Self hosted PaaS - Dokku
Let’s think of a micro Heroku as a PaaS, but you can dive in and check what is going wrong. For me, this is Dokku for smaller use cases, where you don’t need many servers to run a web application.
If you need a multi-server setup, which is better to scale for bigger applications, maybe check Kamal Deploy by DHH. Check the documentation on the project page https://kamal-deploy.org.
Dependent on your requirements you can start with Dokku and switch later to a multi server solution with many servers or move to Heroku.
Dokku Basic Setup
First of all, you need to install an operating system on your server. The one supported by Dokku documentation are listed here: Dokku System Requirements. Dependent on your provider, you can use a DigitalOcean Droplet Installation.
Dependent on your use case and setup check the current Dokku documentation: Getting Started with Dokku.
ATTENTION: Dokkus default setup doesn’t use any remote API or CLI to control
your instances as Heroku does. You have to run all dokku
commands on your
remote machine, where you have installed Dokku (in this tutorial I call the
machine dokku.example.com
).
Add a swap space to virtual machine
Most virtual machines don’t have a swap space, which we consider as essential to stop crashes due to short memory (RAM) limit.
Check you setup first by running free -m
and look work Swap
:
$ free -m
total used free shared buff/cache available
Mem: 1915 794 107 17 1014 912
The setup is of a swap space is pretty simple:
# allocate a file with 2GB space
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
if you check again using free -m
you will see a swap space:
$ free -m
total used free shared buff/cache available
Mem: 1915 794 107 17 1014 912
Swap: 2047 0 2047
HINT: Dependent on your system setup you can have a local partition or a special mounted space, which you may use for the swap file. A machine built it raid array or a SSD drive are great for swap.
Prepare Dokku for Ruby on Rails
Information: You don’t need to run all remote commands using sudo
or as
root if your user is allowed to use sudo
with or without a password. Dokku
will determine the context and request permissions running sudo by itself.
Tu run a Ruby on Rails Application, you need to have the right plugins installed like Let’s Encrypt and others. This is done by running:
dokku plugin:install-dependencies --core
This is part of the installation instructions and you may have done it already.
Create your application in Dokku
Set up your app in your Dokku instance before you add the required database plugin. You will need instructions on how to link your database to your application, which is written down in the plugins manual.
We will call the app rails7-example
here and refer to it throughout the whole
tutorial:
# create an app in dokku
dokku apps:create rails7-sample
After creating the application, we need to set Ruby on Rails specific environment variables and dependent on used gems their specific environment variables.
Ruby on Rails specific configuration
Set the Ruby on Rails specific environment variables through dokku configuration command:
# Set the Ruby on Rails Environment
dokku config:set rails7-sample RAILS_ENV=production
# Set your Ruby on Rails Master Key to decrypt the encrypted credentials file
dokku config:set rails7-sample RAILS_MASTER_KEY=your_rails_master_key
Install the Dokku Database Plugin
I prefer PostgreSQL as a database. You can use MySQL, MySQL forks like MariaDB, or any other supported database.
You can use these Dokku database plugins:
… and many more. See the list of Dokku-supported and community-supported plugins.
PostgreSQL Dokku database setup
As an example, I will create a PostgreSQL Database called rails7-example-db
through the PostgreSQL Dokku plugin
and link it to my rails7-example
application.
# Install PosgreSQL Dokku Plugin
dokku plugin:install https://github.com/dokku/dokku-postgres.git
# Create a database
dokku postgres:create rails7-example-db
# Link the PostgreSQL database to your application
dokku postgres:link rails7-example-db rails7-example
The Link command will set the DATABASE_URL
environment variable to the
database for the application (the same as Heroku does with Postgres).
If you use another database, take a look at the documentation of the corresponding Dokku plugin. It should be pretty similar and easy to set up.
Prepare your app for deployment
All these steps should be done and committed through git in your repository containing your Ruby on Rails application.
Define platform for C-extensions
If you have developed your Ruby on Rails application on a newer Mac running an ARM processor (Apple called them M1, M2, etc.) and your deployment is on an AMD64 (x86_64) Linux environment, you have to add it to your Gemfile or Gems with C-extensions will fail to build. Run this command and commit the change to your project (this is the same for Heroku):
bundle lock --add-platform x86_64-linux
git add Gemfile*
git commit -m "Added AMD64 (x86_64) Linux support"
Define application server
As on Heroku, you have to define, how to start your application. You can define
it using a Procfile
:
# Procfile starting a service web using Puma as an application server
web: bundle exec puma -C config/puma.rb
In this example we use Puma as application server.
Define Node.js version
If you are using yarn for your assets, you have to define the Node.js version
and yarn version to be used in your package.json
file:
{
"engines": {
"node": "20.x",
"yarn": "1.22.19"
}
}
If you use npm
instead of yarn
, define it this way in your package.json
file:
{
"engines": {
"node": "20.x",
"npm": "10.x"
}
}
Attention: If you have some kind of CI/CD pipeline, you should use the same Node.js version or your CI/CD may complain, that the wrong version of Node.js is used.
Running migrations
Dependent on your use case, you can or even should run database migrations on your application start.
The most simple solution is to add an entry to your Procfile
before starting
the application server:
# run migrations before starting the web server
release: bundle exec rails db:migrate
# Procfile starting a service web using Puma as application server
web: bundle exec puma -C config/puma.rb
As some applications may be deployed to different environments and servers, I
prefer to run the migrations through an app.json
file, which can be aligned to
every environment (like deployment to Heroku and Dokku) from the same
repository. A basic app.json
file for Dokku only looks like this:
{
"scripts": {
"dokku": {
"postdeploy": "bundle exec rails db:migrate"
}
}
}
If you prefer to run migrations by hand, check this command:
dokku run rails7-example bundle exec rake db:migrate
Setup Domain and secure connection using HTTPS
To secure the connection between your Dokku proxy server (nginx by default) and
the client’s browser/application using your ruby on rails application, you have
to define the domain rails7example.com
for your application:
dokku domains:set rails7-example rails7example.com
Use Let’s Encrypt for SSL/TLS Certificates
You have to install the Let’s Encrypt Dokku plugin for your Dokku instance and set a global email used for Let’s Encrypt:
# Install the Let's Encrypt plugin from the official repository
dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
# Set a global email used by the Let's Encrypt plugin for certificates
dokku letsencrypt:set --global email your-email@your-domain.com
Now enable the Let’s Encrypt Dokku
plugin for your application
rails7-example
by running:
dokku letsencrypt:enable rails7-example
Running this command will take some time, as Let’s Encrypt checks your domain setup and sign your request if valid. If it fails, check your A and AAAA records for the domain. They may be wrong.
Let’s Encrypt certificates are valid for a short period of time. The best thing is to setup cron for auto-renewal by running it as a cron job:
dokku letsencrypt:cron-job --add
ATTENTION: If you check for cron jobs managed by Dokku, you won’t find Let’s Encrypt there. It is managed through the plugin itself.
Deploy your application
To deploy your Ruby on Rails application called rails7-example
to your Dokku
instance (as an example called dokku.example.com) from your development machine
run these commands:
# Add your Dokku instance as remote repository for deployment
git remote add dokku dokku@dokku.example.com:rails7-exameple
# Deploy by pushing your main branch to the remote repository Dokku within the
# master branch
git push dokku main:master
If everything worked, your application should have been deployed and should be
accessible under your select domain e.g. rails7example.com
.
Additional services through plugins
If your application requires a job scheduler like
Sidekiq or
Resque, you have to install the Dokku Redis
Plugin and set up your scheduler through
Procfile
.
Setup Redis through Dokku Plugin
Install the Dokku Redis Plugin and add
it to your application rails7-example
:
# Install Redis through Dokku
sudo dokku plugin:install https://github.com/dokku/dokku-redis.git redis
# Create redis instace for your application
dokku redis:create rails7-example-redis
# Add redis to your application and promote it through REDIS_URL
dokku redis:link rails7-example-redis rails7-example
The link command will provide a REDIS_URL
with all the data Ruby on Rails will
need to use it. Now you need to check how
Resque or
Sidekiq look for the
Redis instance they need.
Now you need to setup a worker to be launched on startup in you Procfile
for
Sidekiq:
# Add a worker to your Procfile for Sidekiq
worker: bundle exec sidekiq -C config/sidekiq.yml
For the Resque Scheduler, this is pretty similar:
# Add worker to your Procfile for Sidekiq
worker: env QUEUE=* bundle exec rake resque:work
Now commit the changes and deploy your application.
If Dokku will not spawn your worker instances by default, you can enable the worker scaling by running:
dokku ps:scale rails7-example web=1 worker=1
Afterward check the status by running:
dokku ps:inspect rails7-example
Dokku has great documentation. Check the Process Management documentation for more details how to manage and to scale your processes for your needs.
Summary
Dependent on the use case of your application, it is enough to run a single hardware or virtual server for your application. By using Dokku you have a great platform to do all the tasks, you are familiar with by using Heroku with more control of the application and without many of the disadvantages Heroku has for you.
Newsletter
See Also
- Fixing require LoadError as an example for the matrix gem
- Fix Flaky Rails System Tests caused by slow scrolling or animations
- Howto migrate from Webpacker to jsbundling-rails in Ruby on Rails
- Howto migrate from Webpacker to cssbundling-rails in Ruby on Rails for CSS
- Howto remove sprockets-rails from you Ruby on Rails project