Stepping off the Rails - adventures with Sinatra (Part 2)
In this episode, our trusty adventurer actually runs his Sinatra app, and deploys it to Passenger on shared hosting. See Part 1 for details of building the application.
Launching locally: two ways to skin a cat
I realized that in Part 1 I did not cover launching the app to hit it from the browser. It’s easy! To launch the app, use ruby app_name.rb
. This will fire up a mongrel instance on port 4567
for your viewing pleasure.
Once you have defined your rack configuration (see below), you can also run the app inside rack locally with rackup config.ru
to test your rack settings. This will launch rack inside mongrel on port 9292
. Thanks to @jnunemaker for the tip. In fact, John’s article is what I used as a basis for my deployment, with a few changes to support file based view templates.
Rack Configuration
To configure the application to run on rack under Passenger, you need to define a rack configuration file. Passenger expects that file to be named config.ru
(see Deploying a Rack-based Ruby application in the Passenger documentation). Here is a minimum config.ru
to run the application:
require 'rubygems' require 'sinatra' disable :run require 'urlunwind.rb' run Sinatra.application
The only real configuration there is disable :run
. This prevents Sinatra from starting a mongrel on port 4567
when the app file is required. This configuration would work for an app that uses no file based templates, and does not reference anything in public/
. If you run this config with rackup on url_unwind (rackup config.ru
), you’ll see errors like:
Errno::ENOENT: No such file or directory - /opt/local/bin/views/index.haml
This is because rack by default sets its base directory to be the path to the called executable (and on my machine, rackup
lives in /opt/local/bin/
). So we’ll need to explicitly set the paths:
require 'rubygems' require 'sinatra' disable :run set :views, File.dirname(__FILE__) + '/views' set :public, File.dirname(__FILE__) + '/public' set :app_file, __FILE__ require 'urlunwind.rb' run Sinatra.application
Now the app should run properly with rackup
.
Logging
Sinatra uses rack’s built in logging to log requests, and these log messages get printed to standard out. It would be nice to log these to a file, and we can do that as well in config.ru
(based on this tip from Chris Schneider). We’ll also need to turn error raising back on, since by default Sinatra swallows errors in production mode:
require 'rubygems' require 'sinatra' disable :run set :env, :production set :raise_errors, true set :views, File.dirname(__FILE__) + '/views' set :public, File.dirname(__FILE__) + '/public' set :app_file, __FILE__ log = File.new("log/sinatra.log", "a") STDOUT.reopen(log) STDERR.reopen(log) require 'urlunwind.rb' run Sinatra.application
This allows you to add your own logging messages as well, just puts
, print
, or p@ whatever you want to log. *Note:* you will need to create the @log/
directory for this to work.
Deploy with Capistrano
I used Capistrano to deploy to Dreamhost, and based my Capfile of off John’s directions here. I modified it a bit to pull from github, and to create the tmp/
and log/
directories if they do not exist. Here is my Capfile:
#-*-ruby-*- load 'deploy' if respond_to?(:namespace) # cap2 differentiator default_run_options[:pty] = true # be sure to change these set :user, 'app_user' set :domain, 'urlunwind.com' set :application, 'url_unwind' set :git_path_prefix, "git@github.com/tobias/" # the rest should be good set :repository, "#{git_path_prefix}#{application}.git" set :deploy_to, "/home/#{user}/#{domain}" set :deploy_via, :remote_cache set :scm, 'git' set :branch, 'master' #set :git_shallow_clone, 1 set :scm_verbose, true set :use_sudo, false server domain, :app, :web namespace :deploy do task :restart do run "test -d #{current_path}/tmp || mkdir #{current_path}/tmp" run "test -d #{current_path}/log || mkdir #{current_path}/log" run "touch #{current_path}/tmp/restart.txt" end end
Since the Sinatra gem is not installed on Dreamhost, I put it in vendor/
within the app. Since it is not a gem, it must be required with the full path to the base .rb
file, both in config.ru
and in the app file itself (urlunwind.rb
in this case). Here is the final config.ru
:
require 'rubygems' require 'vendor/sinatra/lib/sinatra.rb' disable :run set :env, :production set :raise_errors, true set :views, File.dirname(__FILE__) + '/views' set :public, File.dirname(__FILE__) + '/public' set :app_file, __FILE__ log = File.new("log/sinatra.log", "a") STDOUT.reopen(log) STDERR.reopen(log) require 'urlunwind.rb' run Sinatra.application