Setting up Dokku on Digital Ocean

This is a step by step tutorial that will walk you through setting up a Ruby on Rails application through Docker on Digital Ocean.

Prerequisites

Step 1: Create a digital ocean droplet

If you have a real domain available use that as your hostname.

Select the dokku app from the list of applications.

Add your SSH key and create the droplet.

Step 2: Set up Custom Domain (Optional)

Dokku is your own personal Heroku. If you are familiar with Heroku you know that it runs apps on the domain herokuapp.com. So if you deploy an app called hello it will be accessible at hello.herokuapp.com.

When you set up dokku, you can create your own app domain.

In your DNS settings create the following two A records.

apps.yourdomain.com   ---> ip of the droplet  
*.apps.yourdomain.com ---> ip of the droplet

This means that if you deploy an app called hello it will be accessible at hello.apps.yourdomain.com. Of course, you can customize this and skip the apps subdomain if you wish.

Step 3: Use /etc/hosts instead of Custom Domain

It will take a while for the DNS records to propagate. If you don't want to set up your own domain for the dokku or if you don't want to have to wait for the DNS to propagate you can set up your /etc/hosts

# /etc/hosts
123.123.123.123     apps.yourdomain.com  

Where 123.123.123.123 is the IP of your digital ocean droplet.

Step 4: Dokku VHOST configuration

We need to check that the domain is configured properly on Dokku. The docs explain that if the hostname cannot be resolved at the time you create your dokku image, the domain may not be set.

To check if the domain was set, log in to your droplet as root.

ssh root@apps.yourdomain.com  

You should not need to enter a password because the droplet is set up to use public key authentication.

Once you're logged in, let's check if the VHOST file exists in

cd /home/dokku  
ls  

In my case, the VHOST file was not there. This was causing dokku to ask me for the dokku user's password when I tried to deploy the app.

root@apps:/home/dokku# ls  
HOSTNAME  VERSION  

If you do not see the VHOST file, you need to create it.

echo "apps.yourdomain.com" > /home/dokku/VHOST  
chown dokku:root /home/dokku/VHOST  

Add your development box's public key to the dokku user. This will allow you to push code without needing a password (sets up public key authentication).

cat ~/.ssh/id_rsa.pub | ssh root@apps.yourdomain.com "sshcommand acl-add dokku dokku"  

Step 5: Install postgres

Let's use the dokku-pg-plugin.

From your remote server

cd /var/lib/dokku/plugins  
git clone https://github.com/Kloadut/dokku-pg-plugin postgresql  
dokku plugins-install  

If you get any nginx-related errors, see the Troubleshooting section below.

Now you should have a few extra pg commands available through dokku:

root@apps:~# dokku help  
    postgresql:console <db>                 Open a PostgreSQL console
    postgresql:create <db>                  Create a PostgreSQL container
    postgresql:delete <db>                  Delete specified PostgreSQL container
    postgresql:dump <db> > dump_file.sql    Dump database data
    postgresql:info <db>                    Display database informations
    postgresql:link <app> <db>              Link an app to a PostgreSQL database
    postgresql:list                         Display list of PostgreSQL containers
    postgresql:logs <db>                    Display last logs from PostgreSQL container
    postgresql:restore <db> < dump_file.sql   Restore database data from a previous dump

Install the postgresql client so you can access the database.

From the remote server

apt-get install postgresql-client-9.3  

Step 6: Create database

On the remote server.

dokku postgresql:create <app-name>  
-----> Creating /home/dokku/daffy/ENV
-----> Setting config vars and restarting daffy
DATABASE_URL: postgres://root:supersecret@172.0.1.1:49155/db  
-----> Releasing daffy ...
-----> Release complete!
-----> Deploying daffy ...
-----> Checking status of PostgreSQL
       Found image postgresql/daffy database
       Checking status... ok.
-----> Deploy complete!

-----> daffy linked to postgresql/daffy database

-----> PostgreSQL container created: postgresql/daffy

       Host: 172.0.1.1
       Port: 49155
       User: 'root'
       Password: 'supersecret'
       Database: 'db'

       Url: 'postgres://root:supersecret@172.0.1.1:49155/db'

Now run this command to set the environment variable DATABASE_URL that will be used in your Rails application's config/database.yml.

dokku postgresql:link <app-name> <database-name>  

Step 7: App Setup

Set up your app as you would for a Heroku deployment.

Procfile

Make sure you have a Procfile in your git repo. I am using puma so my Procfile looks like this:

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

You can read more about Procfiles here.

Test your production setup on your local machine first. Foreman is a program that reads your Procfile and launches your application.

foreman start  

If you don't have foreman installed, you can install it by running gem install foreman

Add git remote

Add your dokku remote to your git repo.

git remote add dokku dokku@apps.yourdomain.com:<app-name>  

Set up config/database.yml

On Heroku, they inject a custom config/database.yml into the app. In many projects database.yml gitignored.

On dokku you will have to make sure config/database.yml is in your repo (not gitignored) and it contains the following for the production environment:

production:  
  adapter: postgresql
  url: <%= ENV['DATABASE_URL'] %>
  encoding: unicode
  pool: 5

Sidenote: If you want to keep config/database.yml gitignored there are some things you can do, but I'm not going to go into that here. Hint: If you're using Puma you can play around with config/puma.rb.

Step 8: Deploy

Exciting! Time to deploy. You deploy dokku apps the same way you would on Heroku.

Push the master branch to dokku.

git push dokku master  

This should trigger the deployment.

Counting objects: 2023, done.  
Delta compression using up to 4 threads.  
Compressing objects: 100% (1236/1236), done.  
Writing objects: 100% (2023/2023), 1.53 MiB, done.  
Total 2023 (delta 1155), reused 1141 (delta 705)  
-----> Cleaning up ...
-----> Building daffy ...
remote: Cloning into '/tmp/tmp.P2f4h7p5tB'...  
remote: warning: You appear to have cloned an empty repository.  
remote: done.  
remote: HEAD is now at 606a3a2... Merge branch 'develop'  
-----> Ruby app detected
-----> Compiling Ruby/Rails
-----> Using Ruby version: ruby-2.0.0
-----> Installing dependencies using 1.6.3
       Running: bundle install --without development:test --path vendor/bundle --binstubs vendor/bundle/bin -j4 --deployment
       Fetching gem metadata from https://rubygems.org/.........
       Installing i18n 0.6.9
       Installing rake 10.3.2
       Installing minitest 5.3.4
       Installing thread_safe 0.3.4
       Installing builder 3.2.2
       Installing erubis 2.7.0
       Installing json 1.8.1
       Installing rack 1.5.2
       Installing mime-types 1.25.1
       Installing polyglot 0.3.4
       Installing arel 5.0.1.20140414130214
       Installing c3-rails 0.3.0
       Installing coffee-script-source 1.7.0
       Installing execjs 2.0.2
       Installing multi_json 1.10.0
       Installing thor 0.19.1
       Installing request_store 1.1.0
       Installing hike 1.2.3
       Using bundler 1.6.3
       Installing rack-cors 0.2.9
       Installing tilt 1.4.1
       Installing temple 0.6.7
       Installing rack-test 0.6.2
       Installing tzinfo 1.2.0
       Installing treetop 1.4.15
       Installing coffee-script 2.2.0
       Installing sprockets 2.11.0
       Installing slim 2.0.2
       Installing puma 2.8.1
       Installing mail 2.5.4
       Installing activesupport 4.1.1
       Installing activemodel 4.1.1
       Installing actionview 4.1.1
       Installing activerecord 4.1.1
       Installing actionpack 4.1.1
       Installing actionmailer 4.1.1
       Installing railties 4.1.1
       Installing gon 5.2.0
       Installing sprockets-rails 2.1.3
       Installing coffee-rails 4.0.1
       Installing d3_rails 3.4.11
       Installing jquery-rails 3.1.0
       Installing slim-rails 2.1.4
       Installing turbolinks 2.2.2
       Installing rails 4.1.1
       Installing pg 0.17.1
       Your bundle is complete!
       Gems in the groups development and test were not installed.
       It was installed into ./vendor/bundle
       Bundle completed (23.01s)
       Cleaning up the bundler cache.
       Detected manifest file, assuming assets were compiled locally

       ###### WARNING:
       Include 'rails_12factor' gem to enable all platform features
       See https://devcenter.heroku.com/articles/rails-integration-gems for more information.

       ###### WARNING:
       You have not declared a Ruby version in your Gemfile.
       To set your Ruby version add this line to your Gemfile:
       ruby '2.0.0'
       # See https://devcenter.heroku.com/articles/ruby-versions for more information.

-----> Discovering process types
       Procfile declares types -> web
       Default process types for Ruby -> rake, console, web, worker
-----> Releasing daffy ...
-----> Deploying daffy ...
=====> Application deployed:
       http://daffy.apps.yourdomain.com

To dokku@apps.yourdomain.com:daffy  
 * [new branch]      master -> master

Step 9: Set up the database

From the remote server, let's create and migrate the database.

dokku run <app-name> rake db:create  
dokku run <app-name> rake db:migrate  
dokku run <app-name> rake db:seed  

Restart the app.

docker restart <docker-container-id>  

To find the container id you can run docker ps -a.

You're done! Go visit your app url.

If anything goes wrong at this point, check out the Troubleshooting section below.

Further Reading

Dokku documentation - http://progrium.viewdocs.io/dokku/index

Troubleshooting

Error while Compiling Ruby/Rails

-----> Compiling Ruby/Rails
       !
       !     Command: 'set -o pipefail; curl --fail --retry 3 --retry-delay 1 --connect-timeout 30 --max-time 30 https://s3-external-1.amazonaws.com/heroku-buildpack-ruby/cedar-14/ruby-2.1.0.tgz -s -o - | tar zxf - ' failed unexpectedly:
       !
       !     gzip: stdin: unexpected end of file
       !     tar: Child returned status 1
       !     tar: Error is not recoverable: exiting now
       !

This error means that you are using an unsupported version of ruby in your Gemfile. Dokku seems to be using the Heroku Cedar buildpacks, so you need to use one of the ruby versions supported by Heroku. Other versions might be support which are not listed on this page. For example, 2.1.2 works as of the writing of this article. Anyway, choose a version that makes sense for your app.

502 Bad Gateway

You visit your app url and gasp you get a 502. This means your Rails server did not start or there is some other reason why nginx can't reach your app.

To find out what went wrong, let's look at the logs

docker logs -f a1b2c3d4e5f6  

Where a1b2c3d4e5f6 is your container id. You can find your container id by running docker ps -a.

In my case, I was missing the config/database.yml file in my Rails app.

If you are not sure if your Rails server is running, you can try accessing it from within the remote server:

curl http://127.0.0.1:`cat /home/dokku/<app-name>/PORT`  

Nginx errors

When running dokku plugins-install I got a bunch of errors that culminated in

dpkg: error processing package nginx-full (--configure):  
 dependency problems - leaving unconfigured

After a bit of googling someone suggested reinstalling nginx.

apt-get update  
apt-get remove nginx  

When prompted, choose N to keep the version of /etc/nginx/nginx.conf that was configured when dokku was installed.

This fixed things and I was able to run dokku plugins-install successfully.

It looks like there have been some significant changes to the nginx-vhosts dokku plugin. Hopefully this fixed the issue.