Andy Jeffries - Taekwondo, Rails and Go

Installing Dokku on a Civo OpenStack cloud server and using it for hosting Ruby on Rails applications

Introduction

user

Andy Jeffries


Installing Dokku on a Civo OpenStack cloud server and using it for hosting Ruby on Rails applications

Posted by Andy Jeffries on .
Featured

Installing Dokku on a Civo OpenStack cloud server and using it for hosting Ruby on Rails applications

Posted by Andy Jeffries on .

Installing/configuring Dokku

Dokku aims to be a simple Heroku-like PaaS (Platform-as-a-Service) that you can use to run your applications on your own server.

For the purposes of this guide, we'll use a new Ubuntu 16.04 1GB RAM instance from Civo.com which is available for just £5 per month. Contact me if you want an invitation code; while Civo.com is in beta you get £20 of free credit to use on instances each month, so you can have a play with Dokku for free. When your instance is running, connect to it via SSH using ssh civo@my.ip.addr.ess and run the following (from Dokku's main documentation site).

wget https://raw.githubusercontent.com/dokku/dokku/v0.7.2/bootstrap.sh  
sudo DOKKU_TAG=v0.7.2 bash bootstrap.sh  

If you get an error saying about curl timing out, you need to edit the /lib/systemd/system/docker.service file and edit the ExecStart line to be like this:

ExecStart=/usr/bin/dockerd -H fd:// -mtu=1450  

Then you should run systemctl daemon-reload && service docker restart and then run the bootstrap.sh command from above again. This is a Docker bug.

Once bootstrapping completes you need to go to http://my.ip.addr.ess in a browser to finish the installation. Verify that the SSH key is correct (the one you used to connect to the instance, should be automatically copied in to Dokku), give it a hostname and choose to "Use virtualhost naming for apps". The hostname I chose was app.andyjeffries.co.uk. Then click "Finish Setup" which completes the installation process. You should then setup in your choice of DNS provider an A record pointing to my.ip.addr.ess (I use and recommend LCN.com for all my domain names) for your app.your.domain subdomain, and setup *.app.your.domain to point to the same IP address.

Our Rails application

We're going to setup a MySQL connected Ruby on Rails application, on a custom domain name and protected with a Let's Encrypt SSL certificate to provide HTTPS support. Let's create a new Ruby on Rails application (on your local machine):

rails new sample -d mysql  

We need to use the "rails_12factor" gem in order to easily help our application work in a 12 Factor style. Edit the Gemfile and add the line:

gem 'rails_12factor', group: :production  

and then run bundle install. We need this to be a git repository in order to deploy it, so let's do that now.

git init  
git add .  
git commit -am "Initial commit"  

Normally at this point you'd add an origin server and push it up to there, but I'll let you figure that out.

Creating a database

Dokku has a MySQL plugin but for now we're going to install MySQL in the base operating system and manually administer the databases. The reason is that it's easier to do this and backup the databases by normal mechanisms.

On the server run the following:

apt-get install mysql-server mysql-client  
nano -w /etc/mysql/mysql.conf.d/mysqld.cnf  
# Change "bind-address = 0.0.0.0"
service mysql restart  
mysql -u root -p mysql  
mysql> create database ruby_rails_sample;  
mysql> grant all on ruby_rails_sample.* to rails@'%' identified by 'password';  

Deploying to Dokku

The default solution for running Dokku management commands is to either SSH to the server to run the commands or each time you type them prefix them with ssh -t dokku@your.host.name. There is a nicer solution though - dokku_client.sh. This is a contributed script in the Dokku repository that you put in your $PATH (e.g. I call it dokku and put it in /usr/local/bin/) and make executable with chmod +x /usr/local/bin/dokku.

You can then use this script to create applications and administer them from your local machine. To create an application it needs to know your Dokku host, so set an environment variable in your ~/.bash_profile or ~/.zshrc called DOKKU_HOST. After creating an application, it will then work using the git remote that it creates to know which Dokku host it should connect to. You can use it exactly the same as if you had SSHed to the server and run the commands on there.

You can test this (after downloading it, putting it in the right place, with the right name, making it executable and setting the DOKKU_HOST environment variable) using dokku apps and it should show =====> My Apps. Now we'll create an application in Dokku for our Rails app and link it to our MySQL database:

dokku apps:create ruby-rails-sample  
dokku config:set ruby-rails-sample DATABASE_URL=mysql2://rails:password@app.andyjeffries.co.uk/ruby_rails_sample  

We can deploy our application with:

git remote add dokku dokku@app.andyjeffries.co.uk:ruby-rails-sample  
git push dokku master  

At this point, it'll be working but without a route for '/' within the application, it will appear broken. As writing a Rails application isn't the point of this post, I'll leave that for you to do on your own.

Changing the domain name

Now it's running, we have two steps to make it production ready - we need to set the domain name to one that doesn't end in app.andyjeffries.co.uk (we'll use sample.andyjeffries.co.uk for now):

dokku domains:add ruby-rails-sample sample.andyjeffries.co.uk  

This works perfectly fine now if you hit http://sample.andyjeffries.co.uk in a browser. We should clean up and remove the auto-generated app.andyjeffries.co.uk subdomain:

dokku domains:remove ruby-rails-sample ruby-rails-sample.app.andyjeffries.co.uk  

Now we need to protect the website using HTTPS.

Enabling TLS provided by Let's Encrypt

Let's Encrypt provides free TLS (used to be called SSL) certificates for hosting websites. Bargain! On the server run the following to install the Dokku Let's Encrypt plugin:

ssh civo@app.andyjeffries.co.uk  
sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git  

Now we can enable letsencrypt for our application:

dokku config:set --no-restart ruby-rails-sample DOKKU_LETSENCRYPT_EMAIL=your@email.address  
dokku letsencrypt ruby-rails-sample  

Let's Encrypt certificates have a short expiry, so we need to setup a cron job to automatically renew them:

dokku letsencrypt:cron-job --add  

Now our site works with HTTPS using the URL: https://sample.andyjeffries.co.uk or http://sample.andyjeffries.co.uk. It's left as an exercise to the reader to configure the Rails application to ensure it redirects to HTTPS (hint config.force_ssl = true).

Switching to a Dokku-based MySQL service (optional)

Dokku can actually run a number of services directly that can be created via the dokku command line. One of them is MySQL. Personally I prefer managing this outside of Dokku, but for completeness I'll describe the process here. First off, let's remove the locally installed MySQL server.

apt-get remove mysql-server  

Now let's install the MySQL plugin by running this command on the server:

sudo dokku plugin:install https://github.com/dokku/dokku-mysql.git mysql  

Now we can create a database using the plugin and link it to our application:

dokku mysql:create ruby-rails-sample-db  

There is a dokku mysql:link command, but for some reason this creates a config setting called DOKKU_MYSQL_FUCHSIA_URL which is weird and should be DATABASE_URL, so using the value for Dsn output by dokku mysql:create, change the mysql:// to mysql2:// because Rails requires that and set it as the correct key:

dokku config:set ruby-rails-sample DATABASE_URL=mysql2://...  

 Custom readiness checks

During deployment Dokku will automatically wait for the process to run before switching over web traffic to the new process, but that doesn't mean it's ready to serve requests nor that it will stay running. There is a great supported mechanism for running custom checks against your application before switching over in a zero-downtime style. Create a file in the root of your project called CHECKS with content like this:

WAIT=3 # Wait 3 seconds between each attempt  
ATTEMPTS=10 # Try 10 times total before abandoning  
/ Welcome to my site
/application.js $(function() {

There are reports that if it doesn't work for you, ensure you have a blank line at the end of the file.

Also note this won't work with our sample application, because we don't currently have a route for root within config/routes.rb that will respond with a page containing "Welcome to my site".

 Migrations

Most Rails applications use ActiveRecord to store content in a database, which brings with it the joy of running migrations. Dokku has built in support for running any commands necessary after deployment and before the checks/switchover is done.

To do this, simply create an app.json file in the root of your project with the following content:

{
  "name": "ruby-rails-sample",
  "description": "A sample Ruby on Rails application",
  "scripts": {
    "dokku": {
      "predeploy": "bundle exec rake db:migrate"
    }
  }
}

This structure is based on Heroku's app.json, but the only supported keys are scripts.dokku.postdeploy and scripts.dokku.predeploy.

 Persistent storage

While it goes against 12 Factor principles, there may be times when you're using a gem like Paperclip, without attaching it to external storage such as Amazon's S3 but you don't want all the uploaded files disappearing on each deploy (shocking, I know!). The way to do this is by creating a local folder and mounting it in to your application. On the server run:

mkdir /var/lib/dokku/data/ruby-rails-sample-images  
chown dokku /var/lib/dokku/data/ruby-rails-sample-images  
dokku storage:mount ruby-rails-sample /var/lib/dokku/data/ruby-rails-sample-images:/app/public/system  

You can mount the same mount point to multiple applications if you want to share files between applications.

It's important to note that this is only attached during build and run processes, so after mounting the storage you need to build a new version.

user

Andy Jeffries