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:

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:

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:

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

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

Step 2: Change permission

Step 3: Format the file for swapping device

Step 4: Enable the swap

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

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

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.

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:

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

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:

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:

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.

Add a git remote to your Dokku application

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

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:

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

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

In routes.rb, add:

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

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

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

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:

Open your gemfile and add the puma gem

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

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

Configure pre-flight checks

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

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:

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:

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:

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 root@your-domain 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:

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

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

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?

  • http://epigene.github.io/ Augusts Bautra

    Great post, very clear. Any news about how to run migrations automatically after deploy? There ought to be a simple, hack-like way to inject rake db:migrate somewhere.

  • Frank

    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

  • http://josediazgonzalez.com/ Jose Diaz-Gonzalez

    Not every buildpack supports something like this – php has the composer.json, node has the package.json, and python has pre_compile and post_compile hooks – though we’re working on a feature in our upcoming 0.5.x release to support running hooks from an app.json file: https://github.com/dokku/dokku/pull/1836

  • Frank

    Great news! Thanks Jose. I will update the post

  • Frank

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

  • Ariff

    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.

  • Frank

    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

  • Ariff

    Ok cool. I guess i should commit my database.yml too. Thank you so much dude! Will try it out in a while.

  • Frank

    That’s great, let me know how it went!

  • Ariff

    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′

  • Frank

    Thanks Ariff, I think I will update the post and add a section about setting environment variables in dokku.

  • Jose Manuel

    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.

  • Frank

    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?

  • Jose Manuel

    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

  • Frank

    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!

  • Karim Tarek

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

  • Frank

    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!

  • Karim Tarek

    Thanks for getting back, Frank. I appreciate it :)

  • Dushyant

    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 ?

  • Frank

    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 dokku@your-app-domain-or-ip logs yourapp and see if you can find a more detailed error message.

  • Dushyant

    Yes I am using 3.4.0 for Puma. I did not see anything in dokku@IP logs app_name. However when I enabled the ‘dokku trace on` (suggested here http://dokku.viewdocs.io/dokku/getting-started/troubleshooting/ for the same error), I see following cause:
    Failed to connect to 172.17.0.3 port 5000: No route to host

  • Frank

    Very strange. I never got this error, not even once.

    maybe you’ll find some hints to resolve your issue here: https://github.com/dokku/dokku/issues/948

    If you manage to fix the problem, let me know and I’ll make sure to update the post. That will help others who might stumble onto the same issue. Good luck!

  • Dushyant

    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

  • http://mddeveloper.com/ Manny

    Thank you for the great tutorial and links to others!
    Definitely saved to favorites.

  • Dushyant

    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.

  • http://ariefrizky.com Arief Rizky Ramadhan

    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!

  • Glenn

    Awesome post ! Just a note, I couldnt get my db migrations to work with ssh dokku@your-domain dokku run myapp rake db:migrate. Instead I had to use ssh root@your-domain dokku run myapp rake db:migrate to make it work

  • Frank

    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 dokku@your-droplet-ip:myapp2)

    Hope this helps.

  • Frank

    Nice catch, thanks! I’ve updated the post.

  • Frank

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

  • Frank

    I’m glad you liked it, thanks for the comment!

  • http://www.equipebeta.com.br/ Equipe Beta

    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 dokku@:myapp

  • Noah

    As a big Rails fan who’s been tinkering with Dokku a lot lately, this post was fantastic! Thanks so much

  • Frank

    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?

  • Frank

    I’m glad you liked it. Thanks!