Dokku lets you setup Rails hosting infrastructure on a simple VPS without much dev ops experience. Although it is easy to get started, a default config might result in very slow and unreliable deployments. In this tutorial, I will describe how I’ve improved my Dokku based Ruby on Rails (NodeJS with Yarn and Webpack) application deployment speed by over 400% using a Docker image Dockerfile.
This tutorial is written in the context of RoR tech stack but solution presented (with small Docker image config tweaks) can be applied to all the server side technologies hosted with Dokku.
Getting started with Docker and Dokku
If you are not familiar with Docker and Dokku, you should check out one on my previous blog posts to get up and running quickly. Once you have a simple Rails app hosted on buildpacks based Dokku setup, you can follow rest of this tutorial to significantly reduce deployments speed.
But first, let me explain why default Dokku deployment tends to be so slow.
“There’s a Heroku buildpack for that…”
Every non-trivial Rails app has multiple system level dependencies. Dokku provides support for them using so-called
buildpacks, the same approach that Heroku is using.
My new project was using the following
- Ruby - a default buildpack for Rails apps
- NodeJS - required for Rails apps using Webpacker and Yarn for frontend dependencies
- Jemalloc - a must-have for modern Rails apps, out of the box reduces memory usage by ~20%
But why buildpacks are so slow?
Buildpack is a list of commands that are executed during a deploy. I am not too much of a Docker expert to understand exactly what’s going on under the hood, but let’s take a look at a sample console output:
Apparently by default buildpacks are not too smart about caching and every single deployment downloads and recompiles some of the application dependencies. No wonder it is slow, and probably not the best way to use your application’s VPS CPU and RAM. What’s worse buildpacks based builds sometimes randomly fail due to 3rd party connectivity issues and network timeouts.
Speed up the deployment with Dockerfile
There is an alternative solution. You can use a custom Docker image instead of multiple buildpacks. The official Dokku documentation mentions it very briefly and describes as a
"Power User" feature.
In practice it’s just a matter of adding one config file and running a couple of bash commands. Switching to Dockerfile reduced deployment time of Abot from over 8 minutes to less than 2.
I will explain how to set this up for a sample Ruby on Rails app using Ruby 2.5.1, NodeJS 8.x.x LTS with Yarn 1.9.4.
Use an official Ruby Docker image
You need to start with adding a
Dockerfile file to the root of your application folder with the following contents:
It installs NodeJS and Yarn on top of the official Ruby 2.5 Docker image
Don’t forget to precompile the assets by adding it as a predeploy step in
Now you just need to remove the
.buildpacks file if you were using it before and remove one config variable:
When you do a git push to dokku remote your Ruby on Rails app will use a Dockerfile instead of buildpacks:
Use a custom Docker image with preinstalled dependencies
Alternatively, you could use my Ruby Jemalloc/NodeJS/Yarn buildpack ([Disclaimer] I am not a dev ops pro. Tips/PRs on how this image could be improved are welcome.). It has an additional advantage of using Ruby binary compiled with Jemalloc for reduced memory usage and NodeJS with Yarn is already in place:
You could also build and publish Dockerfile image yourself but that’s outside the scope of this tutorial.
ENV variables defined by
dokku config:set command are not available during Dockerfile based deployments build time.
bundle exec rake assets:precompile predeploy step will launch your Rails app process, and things could fall apart if some of the required
ENV variables are missing.
You should set them using this command:
It’s not a perfect solution because requires you to duplicate config but works for my use case and must be done only for nonoptional variables.
Useful Docker commands
Here’s a list of commands that might come in handy if you get stuck along the way:
Playing directly with Dockerfile images is a bit lower level than using a default Dokku buildpacks based approach. For me, the speed and reliability of Dockerfile powered deployments was more than worth the effort.