Cyrus Stoller home about consulting

Deploying Sinatra using Capistrano

For many simple web applications Ruby on Rails is overkill. In this post, I’m going to explain how to deploy a simple game to learn people’s names that uses Sinatra for its backend. I’m going to assume that you have already provisioned your server with the necessary dependencies using a technique like this one.

Deploying this application took longer than I expected. Hopefully this post will save you from having to reinvent the wheel.

Ingredients

Setting Up Sinatra

I like to write Sinatra applications in the modular style. This means that I create a server.rb file with the following structure:

# server.rb
require 'sinatra/base'

class GuessWho < Sinatra::Base
  # ... app code here ...
end

Because this will not run on its own, I create a basic rackup file to run the application and add middleware. The most basic form is:

# config.ru
root = ::File.dirname(__FILE__)
require ::File.join( root, 'server' )

run GuessWho.new

For this application I also wanted to add HTTP Basic Authentication, so only people at my company could play. To do this, I added some middleware by adjusting the rackup file to:

# config.ru
root = ::File.dirname(__FILE__)
require ::File.join( root, 'server' )

ENV["RACK_ENV"] ||= "development"

require 'dotenv'
Dotenv.load

unless ENV["RACK_ENV"] == "development"
  use Rack::Auth::Basic, "IDEO" do |u, p|
    u == ENV["USERNAME"] && p == ENV["PASSWORD"]
  end
end
use Rack::Static, :urls => ["/css", "/img", "/js"], :root => "public"
use Rack::CommonLogger

run GuessWho.new

This loads environment variables for the HTTP Basic Authentication username and password. I also wanted to protect files that ordinarily would have been served from the public directory. I did this by adding the Rack::Static middleware.

By writing Sinatra applications in the modular style, I can easily serve multiple applications using the same server by just adding a couple lines to the rackup file.

I’ll use this config.ru for the rest of the deployment process.

Server Provisioning

I use Puppet Manifests to install rbenv, nginx, and SQLite3 on my Ubuntu 14.04 LTS server. (To install SQLite3 I added libsqlite3-dev to my common.yml file.) At this point, the rest of the deployment is done by running commands from within the project directoy on my laptop. Capistrano will run the remaining SSH commands to make this application available.

Capistrano Recipes

Most of the documentation on the Capistrano website details how to deploy Ruby on Rails, but it doesn’t go over which parts you can disregard when deploying a simpler Sinatra application.

First, you need to install Capistrano.

$ bundle exec cap install

This creates a basic file structure that you need to fill in to tell Capistrano what it needs to do to deploy your application.

I first update my config/deploy.rb file. Follow the comments and include a link to your project repository. Then, I setup the linked_files and linked_dirs that are going to be the same between deployments. These are things like uploaded files and my database that are not committed into my git repository.

# config/deploy.rb
## Linking my environment variables and database
set :linked_files, fetch(:linked_files, []).
  push('.env', 'data/people.sqlite3.db')

## Linking my log files, system info for thin & nginx, and uploads
set :linked_dirs, fetch(:linked_dirs, []).
  push('log', 'tmp/pids', 'tmp/sockets', 'public/img/thumbnail')

For more specifics, look at the complete project.

Next, I create a helper function to simplify using template files in my recipes.

# lib/capistrano/tasks/helper.rb
def template(filename)
  File.read(File.expand_path("../../templates/#{filename}", __FILE__))
end

Now, I created the following files.

lib/
└── capistrano
    ├── tasks
    │   ├── env.cap   # To manage my environment variables
    │   ├── helper.rb # To manage templates
    │   ├── nginx.cap # To manage nginx
    │   ├── setup.cap # For initial setup
    │   └── thin.cap  # To manage the thin server
    └── templates
        ├── env.production
        ├── nginx.conf.erb
        ├── thin.yml.erb
        ├── thin_init.sh.erb
        └── thin_log_rotate.conf.erb

In env.cap, I define two tasks. The first (env:update) updates the .env file on the server and then restarts the application to pickup the new environment variables. The second (env:upload) only uploads the .env file.

In thin.cap, I define tasks to upload the thin config file (thin:app_config) and an init.d file to run the application as a service (thin:initd). I also defined tasks to start the service on boot (thin:on_boot) using update-rc.d and rotate the log files (thin:logrotate_d) using logrotate. Lastly, I setup tasks to start, stop, and restart the thin server and a convenience task to run the initial setup tasks (thin:setup).

In nginx.cap, I defined tasks to upload and symlink the nginx.conf file and for managing the the nginx server. This is pretty straight forward. The main thing to pay attention to is that I’m passing the requests via a socket instead of via a designated port. It’s important to be consistent between the thin config and the nginx config, but otherwise this choice is inconsequential.

Lastly, the setup.cap file is just a convenience task to run once before first deploying the application to get everything setup.

One last gotcha: for some reason I needed to explicitly close the ActiveRecord connection at the end of each request (seems like others are finding this too).

Hope this is helpful. Happy hacking.

Category Tutorial