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.
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.
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.
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.