Deploy your Rails applications like a pro with Dokku and DigialOcean

UPDATE September 19th, 2016.

This tutorial has been updated to target Dokku version 0.6.5.

After reading this tutorial, you will be able to:

  • Create your own server (droplet) on the cloud using the DigitalOcean cloud architecture. (I will also share with you a link that will give you $10 credit at DigitalOcean).
  • Install your first DOKKU plugin. In this case, a Postgresql database plugin
  • Automate your database migrations using the app.json manifest file
  • Create a swap file to prevent memory issues when using the cheapest droplet type (512 M)
  • Setup zero downtime deployments using the CHECKS feature
  • Setup a Procfile to run your application with something better than WEBrick

I’ve tested each step of this tutorial multiple times so you should not run into any issues. If you do however, please leave me a comment at the end of this post and we will sort it out together!


Heroku has become the standard to host Ruby On Rails web applications. It is understandable because Heroku has such a great infrastructure. Deploying is a matter of typing “git push heroku master” and you’re pretty much done!
The thing is, if you are part of a small development team or you are a freelancer, the cost of using Heroku for all your clients / projects might become a real issue for you. This is where Dokku comes in! But what is Dokku?
The description on the Dokku home page is pretty self-explanatory:

The smallest PaaS implementation you’ve ever seen. Docker powered mini-Heroku in around 200 lines of Bash

So, there you have it. A “mini-heroku” that you can self-host or, better perhaps, use on an affordable cloud infrastructure such as DigitalOcean (use that previous link to get a $10 credit). Small teams and freelancers can now deploy like the pros at a fraction of the cost. Follow this tutorial and soon, you too, will be able to deploy your Rails apps simply by typing: git push dokku master. How neat is that? Sure you will have some configuring to do, but the overall process is not that complicated. This tutorial will show you how to get there.

Get your $10 credit here:

Are you ready for the tutorial…?

DigitalOcean

First, create the droplet on DigitalOcean.
Droplet creation
Then you have to choose the size of the droplet. Let’s choose the cheapest option (Small teams and freelancers love cheap options. We’re broke!)
Be cheap
Choose your image! Don’t miss this step, it’s very important. Don’t choose a Rails preset or a Ubuntu image. Remember, we want Dokku!
Dokku!
Add your ssh key(s) for a more secure access to your droplet.
SSH Keys
Then select the number of droplets to create and choose a hostname
Choose an hostname
Finally, click on the “Create” button and wait until your droplet is fully created!
Waiting, I hate waiting...
The DigitalOcean part is done. Now we have to make sure we can log in to our droplet

Connect to our droplet via SSH

Open a terminal window and connect to your droplet, like this:

ssh [email protected]

Make sure the Dokku user can connect using your SSH key as well

When you will deploy your app with git, the “dokku” user will be used instead of root, so you need to make sure that this user can connect to your droplet. I’m not sure if this is supposed to be configured automatically when you create your droplet, but it didn’t work for me. Have a look at the file located in /home/dokku/.ssh/authorized_keys (on your droplet). If it’s empty like it was for me, run this command:

cat /root/.ssh/authorized_keys | sshcommand acl-add dokku dokku

Add a swap file!

Since we chose the cheapest option (512 M), we will run into memory problems when we will deploy our Rails application. Rails assets compilation will make your deploy fail. Don’t worry though, your web application will still be running smoothly. What’s the solution if we are determined to use our cheap 512M droplet? Simple, we just add a swap file as explained in this StackOverflow answer. What follows is (almost) an exact copy of that answer.
To see if you have a swap files:

sudo swapon -s

No swap file shown? Check how much disk space space you have:

df

To create a swap file:
Step 1: Allocate a file for swap

sudo fallocate -l 2048m /mnt/swap_file.swap

Step 2: Change permission

sudo chmod 600 /mnt/swap_file.swap

Step 3: Format the file for swapping device

sudo mkswap /mnt/swap_file.swap

Step 4: Enable the swap

sudo swapon /mnt/swap_file.swap

Step 5: Make sure the swap is mounted when you Reboot. First, open fstab

sudo nano /etc/fstab

Finally, add entry in fstab (only if it wasn’t automatically added)

/mnt/swap_file.swap none swap sw 0 0

Great, now we have our swap file. What’s next?

Create our application in Dokku

If you type the dokku command, the list of commands for dokku will be displayed on the screen. You should study it as it is very instructive, but for now we will simply use the dokku apps:create command to create our application.

dokku apps:create myapp

This will create a container for your new app.

Database? Sure, let’s use Postgres

To interact with a postgres database on Dokku, you need to use a plugin. I had success with this one, so:

dokku plugin:install https://github.com/Flink/dokku-psql-single-container

Once it’s installed, feel free to type the dokku command again. It will show you new commands starting with “psql”. Let’s create our database

dokku psql:create myapp

Done! But, you might ask, where are the credentials to access this database from our future Rails application? Good news! They have already been generated for you, type:

dokku config myapp

And you will be shown a list of all environment variables for your app. In this list you will find your connection string for your postgres database. Great!

Speaking of environment variables…

Thanks to Ariff in the comments for asking questions about environment variables. The following section is a recap of what was discussed in the comments.
To configure a new environment variable for a given application, you do the following:

dokku config:set myapp SOME_SECRET_VAR='hello'

Note that you don’t have to manually set the SECRET_KEY_BASE environment variable which is used in the secrets.yml file of your Rails application. This is because the ruby buildpack already does this for you. As you can see in the source code, SECRET_KEY_BASE is set to a randomly generated key (have a look at the setup_profiled and app_secret methods).

Create our Rails app locally

Switch to your local workstation and create a new rails app.

  rails new myapp
  cd myapp
  git init .

Add a git remote to your Dokku application

   git remote add dokku [email protected]:myapp

Open your database.yml and add your Dokku environment variable:

development:
  adapter: postgresql
  database: myapp_dev
  encoding: utf8
  host: localhost
  username: somedevusername
  password: somepassword
test:
  adapter: postgresql
  database: myapp_test
  encoding: utf8
  host: localhost
  username: somedevusername
  password: somepassword
production:
  adapter: postgresql
  url: <%= ENV['POSTGRESQL_URL'] %> #This is the environment variable created by our Dokku command earlier
  encoding: unicode
  pool: 5

Off topic: Why not take this opportunity to use environment variables for all your secrets?
As for the Gemfile, make sure it has the following lines:

ruby '2.2.3' #or any other ruby version
gem 'rails_12factor', group: :production #rails library tuned to run smoothly on Heroku/Dokku cloud infrastructures
gem 'pg' #postgres gem
#...

We will also create a default controller to have somewhat of a functioning application. On your local worstation, run:

./bin/rails g controller static_pages

Create a new file named home.html.erb in app/views/static_pages and add the following:

<p>Hello world!</p>

In routes.rb, add:

root 'static_pages#home'

Are you ready? Run bundle install, commit everything then type:

git push dokku master

If you did everything correctly, you should see something like this after you pushed to dokku.(I edited the output to keep it brief).

Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 282 bytes | 0 bytes/s, done.
Total 2 (delta 1), reused 0 (delta 0)
-----> Cleaning up...
-----> Building myapp from herokuish...
-----> Adding BUILD_ENV to build environment...
-----> Ruby app detected
-----> Compiling Ruby/Rails
-----> Using Ruby version: ruby-2.2.3
-----> Installing dependencies using bundler 1.9.7
       Running: bundle install --without development:test --path vendor/bundle --binstubs vendor/bundle/bin -j4 --deployment
...
=====> myapp container output:
       [2015-12-03 15:06:47] INFO  WEBrick 1.3.1
       [2015-12-03 15:06:47] INFO  ruby 2.2.3 (2015-08-18) [x86_64-linux]
       [2015-12-03 15:06:47] INFO  WEBrick::HTTPServer#start: pid=8 port=5000
...
-----> Setting config vars
       NO_VHOST: 1
-----> Creating http nginx.conf
-----> Running nginx-pre-reload
       Reloading nginx
-----> Setting config vars
       DOKKU_APP_RESTORE: 1
-----> Shutting down old containers in 60 seconds
=====> 69d9e4f83ec73f18c72a81d13cf42525ba2369cda3b440555b57a0cb9bf7835a
=====> Application deployed:
       http://dokku:32772 (container)
       http://dokku:80 (nginx)

Open a browser and type your droplet ip address + the port shown above, in this case it’s 32772. You should see an ugly “Hello World!”, congratulations!

WEBrick? Nah, let’s use Puma

We didn’t specify a ruby application server so the default WEBrick was used. WEBrick is ok in development but in production it is not a good idea. On your local workstation, create a new file in config/puma.rb

workers 1 #Change this to match the number of cores in your sever. Our cheap (but nice) 512M droplet gives us only 1 core
threads_count = Integer(ENV['MAX_THREADS'] || 5)
threads threads_count, threads_count
preload_app!
rackup      DefaultRackup
port        ENV['PORT']     || 3000
environment ENV['RACK_ENV'] || 'development'
on_worker_boot do
  # Worker specific setup for Rails 4.1+
  # See: https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#on-worker-boot
  ActiveRecord::Base.establish_connection
end

Now let’s create a procfile to tell Dokku we want to use puma instead of WEBrick
Create a file at the root of your Rails app called Procfile (notice the capital P) and enter the following line:

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

Open your gemfile and add the puma gem

gem 'puma'

Run bundle install. Commit everything and push to dokku once more. Have a look at the output, you should see something like this:

=====> myapp web container output:
       [8] Puma starting in cluster mode...
       [8] * Version 3.3.0 (ruby 2.3.0-p0), codename: Jovial Platypus
       [8] * Min threads: 5, max threads: 5
       [8] * Environment: production
       [8] * Process workers: 1
       [8] * Preloading application
       [8] * Listening on tcp://0.0.0.0:5000
       [8] Use Ctrl-C to stop
       [8] - Worker 0 (pid: 162) booted, phase: 0

If for some reason you don’t see anything about Puma in the output. Try looking at the logs by typing:

ssh [email protected] dokku logs myapp

Configure pre-flight checks

Something might have caught your attention when we deployed our application:

-----> Running pre-flight checks
       For more efficient zero downtime deployments, create a file CHECKS.
       See http://progrium.viewdocs.io/dokku/checks-examples.md for examples
       CHECKS file not found in container: Running simple container check...

Checks in Dokku are a way to setup zero downtime deployments. You don’t want your users to get an error page while your server is restarting. Since we have not created any custom check, dokku run a default check that simply make sure that the new container is up and running before pointing to the new app. The problem is it will not check if puma has been fully loaded. Let’s create a super simple check to make sure our Rails application is available.

At the root of your app, create a file named CHECKS and add the following:

WAIT=8 #Wait 8 seconds before each attempt
ATTEMPTS=6 #Try 6 times, if it still doesn't work, the deploy has failed and the old container (app) will be kept
/check_deploy.txt deploy_successful

Important: Leave an empty line at the end of this file, otherwise Dokku might not detect your check. Is this a bug? I don’t know… but it took me a while to figure this one out!

Now create a file called “check_deploy.txt” in your rails public directory and add the text:

deploy_successful

In other words, dokku will try 6 times to obtain the “deploy_successful” string after calling “/check_deploy.txt”.
Push everything to dokku and verify the output. You will probably see something like that:

-----> Running pre-flight checks
-----> Attempt 1/6 Waiting for 8 seconds ...
       CHECKS expected result:
       http://localhost/check_deploy.txt => "deploy_successful"
-----> All checks successful!

Domains and VHOST

So far we’ve been relying on our droplet ip address for accessing our application. It’s better to have a real domain name pointing to Digital Ocean. I invite you to read this tutorial to setup your domain name correctly with Digital Ocean.

Database migrations

Before Dokku 0.5, it was not really possible to have your database migrations run automatically on deploy. You had to do it in two steps. First you deploy, then you migrate by typing: ssh [email protected] dokku run myapp rake db:migrate

Fortunately, we can automate the process now that Dokku supports the app.json manifest file. Create a app.json file in the root of your repository and add this:

{
  "name": "myapp",
  "description": "Dummy app to go along the dokku tutorial found on rubyfleebie.com",
  "keywords": [
    "dokku",
    "rails",
    "rubyfleebie.com"
  ],
  "scripts": {
    "dokku": {
      "postdeploy": "bundle exec rake db:migrate"
    }
  }
}

Let’s create a dummy model to see if the migrations will be run.

./bin/rails g model Book

Then we commit everything and push to dokku. THe output should look like this:

-----> Running post-deploy
-----> Attempting to run scripts.dokku.postdeploy from app.json (if defined)
-----> Running 'rake db:migrate' in app container
       restoring installation cache...
       Migrating to CreateBooks (20160405194531)
       == 20160405194531 CreateBooks: migrating ======================================
       -- create_table(:books)
          -> 0.0139s
       == 20160405194531 CreateBooks: migrated (0.0142s) ==========

How cool is that? I hoped you enjoyed this tutorial. Your comments are appreciated!

Troubleshooting

Dushyant in the comments had some errors on deploy. He found out that his problem was related to the numbers of containers configured when using DigitalOcean 5$ plan. I didn’t run into this problem myself, so here is what Dushyant says about it:
« Finally I found the solution. My previous solution got me working but ultimately that wasn’t the true solution.
It is happening because of containers and because of 5 dollar plan.
You can get the list of containers by this command
docker ps
Then remove the unwanted containers
docker rm -f docker_id
»

What’s next?

How about automating your database backups and storing them on a zero-knowledge cloud architecture?

40 thoughts on “Deploy your Rails applications like a pro with Dokku and DigialOcean

    1. Thanks for your comment Augusts. Unfortunately I didn’t have time to check this yet.
      I’m almost certain it’s a matter of adding a docker “deploy”option for your app. Connect to your droplet as root and have a look at the following command:
      dokku docker-options yourapp

    2. Augusts Bautra: Since Dokku 0.5 you can run your database migrations on deploy. Have a look at the updated post!

  1. Hey Frank thank you so much for the post. Exactly what i was looking for. One question though, how do you manage your secrets? I get the error Rake aborted! Devise.secret_keys not set. I have my secrets hardcoded in secret.yml and not commited to git. Is there a way to load the file using dokku? Something like rake heroku:secrets RAILS_ENV=production ?
    I saw your other post recommending using environment variables. Would that mean i commit the secrets file to git ?
    Many thanks.

    1. Hello Ariff, I’m glad my tutorial helped you!
      The “12factor way” (which is embraced by Dokku/Heroku), encourages you to set your secrets in environment variables. The recommended way, then, is to commit secrets.yml to your repo and use an environment vaiable instead of an hardcoded value for secret_key_base.
      Then, the Ruby buildpack will automatically set the SECRET_KEY_BASE environment variable based on a pre-generated random value, as you can see here: https://github.com/heroku/heroku-buildpack-ruby/blob/8d1ed781d9c918c0fd6104032c60f872055e6664/lib/language_pack/rails41.rb#L18

      1. Ok sweet. It worked great. Just for remember to set up the rest of the env variables with:
        dokku config:set myapp SECRET_KEY=’somesecret123456′

  2. Hi Frank. I have a question kinda newbie, when you deploy for the first, second, n times is the application build again from cero? all the gems are installed again and the gemfile.lock is a new one or is overwrite?. I think I’m having problems with my gemfile.lock when I try to deploy.

    1. Hi Jose. The gems are fetched and installed from the external source (i.e. rubygems.org) only if they are not already installed on the target machine. The gemfile.lock file is only updated if new gems are installed when the “bundle install” command is executed. If the gems are already there, there will be no “reinstall” and gemfile.lock will not be updated.
      What is the problem you have with gemfile.lock exactly? I may be able to help you out. Do you have an error message?

      1. Hi Frank. You can see the error in the img below. Everything was working fine,I was doing “git push app master” with no problems but after a while I got that error. I specify the Ruby version 2.2.2 in the gem file and then for mistake I change it to 1.9.3 and did a push and then switch back to 2.2.2. A friend told me that maybe those change of the ruby version in the gemfile mess the gemfile.lock

        1. Hmm, something strange definitely happened there. A few things you can try:
          1. Make sure that Gemfile.lock is not .gitignored
          2. Have a look at the production log file (dokku logs yourappname)
          3. Maybe the error is related to Puma? Try reverting back to Webrick by temporarily removing the Procfile
          Good luck!

  3. Hey Frank, great post, thanks.
    Just wondering if you have any experience migrating a database from Heroku to Dokku? If so, care to share 🙂

    1. No experience, but I’d guess it’s a matter of dumping the DB on heroku and restoring it on dokku using a plugin (e.g. dokku psql:restore myapp dumpfile_location). Thanks for the comment!

  4. Great post indeed!
    I am having little problem with it. Sometimes I am able to deploy successfully, sometimes not.
    Error: ! [remote rejected] master -> master (pre-receive hook declined)
    I am about to launch my application but this error is giving me nightmares. I tried solutions you suggested in some comment and reverting back Puma worked. But I cannot do this all the time. Is there any workaround ? any alternative other than Webrick ?

    1. Just guessing here, are you using the latest Puma version (3.4.0)? What is the complete error message at delploy? The pre-receive hook declined message is coming from GiT and it is displayed whenever the deploy fail.
      Try doing ssh [email protected] logs yourapp and see if you can find a more detailed error message.

          1. Thank you for taking out time to inspect the problem. I have gone through the link you provided already. But no luck. However I have found a fix to this problem. I have to find this file /etc/defaults/docker and uncomment the line::
            DOCKER_OPTS=”–dns 8.8.8.8 –dns 8.8.4.4″
            And sudo service docker restart
            ref: https://github.com/dokku/dokku/issues/668

          2. Hey Frank
            Finally I found the solution. My previous solution got me working but ultimately that wasn’t the true solution.
            It is happening because of containers and because of 5 dollar plan.
            You can get the list of containers by this command
            docker ps
            Then remove the unwanted containers
            docker rm -f docker_id
            That is it!
            Hope it helps someone and sorry for the late response.

          3. Hi Dushyant, sorry for the late response too! I’ve updated the post to add your solution (Section Troubleshooting).

  5. Great tutorial! I’ve successfully deployed two rails apps with ease.
    Now, I want to take a step further, I have multiple rails apps that I want to push into one droplet (same dokku). Is it possible to do so? If yes, can you show me how? Much appreciated!

    1. Hi Arief, I’m glad you liked the tutorial!
      Sure, you can push multiple apps on the same droplet. Simply create your new app the same way we did in the tutorial. (eg. dokku apps:create myapp2).
      Then, create your pg database. (eg. dokku psql:create myapp2)
      Finally, associate your new git repo to this app (e.g. git remote add dokku [email protected]:myapp2)
      Hope this helps.

      1. Thank you for the reply, Frank! I’m having issue pointing the domain to each of the app. For instance, I have domain1.com pointed to the myapp1 and domain2.com pointed to the myapp2. However, whenever I go to domain2.com it leads to the myapp1. Any solutions? Thank you in advanced!

  6. Hello man! I’m trying but I’m doing something wrong.
    When I try to make git push dokku master
    fatal: ‘myapp’ does not appear to be a git repository
    fatal: Could not read from remote repository.
    Please make sure you have the correct access rights and the repository exists.
    If I run dokku logs myapp:
    Application’s container not found
    But if I run dokku apps:create myapp, i receive this message:
    Name is already taken
    What I’m doing wrong? oO
    In git remote
    dokku [email protected]:myapp

    1. Weird indeed. It might sounds obvious, but are you sure that you have created your Dokku application on your remote droplet and not on your local workstation?

  7. Excelent post !, i have some questions, can i use dotenv-rails on dokku? and is very important have db:migrate after each deploy? you can do it manually and is the same right?

  8. Hey Frank! I always seem to end up here when i’ve got rails/dokku issues. I followed this tutorial the last time when it was still 0.5.x. And now I’m trying to upgrade dokku to either 0.6.x or 0.7.x. I see that you’ve done that too. Before doing so I’d like to dump my database incase something goes wrong. For some reason, I just can’t seem to do it. Been running..
    $ dokku psql:dump shredx-production database.dump
    as root user but then I can’t seem to find the file. can’t find it either when executing the command as a dokku user. Any idea how can i get that file. Running
    $ find / -name “database.dump”
    don’t seem to be doing anything as root user and I get permission denied when running as dokku user.
    Do you reckon I could just do the upgrade without doing a db backup?
    Any ideas or help on this would be greatly appreciated.
    Cheers

Leave a Reply

Your email address will not be published. Required fields are marked *