Deploying on Dreamhost

This document assumes that you know how to operate the basics of the following required software. Me? I'm a Linux Systems Administrator, but a Rails deployment newb. I've decided to cut my teeth at Deployment using a mix of Dreamhost/Radiant. Things look to be going OK.

Know these tools...

  • svn
  • ssh
  • rails

Recent Updates to Document

  • 7/26/06 - Caylan
    • Added recent updates, how meta!
    • Added bluecloth dependency in vendor
    • Modified tmp directory creation to use rake task
  • 8/3/06 - Jason
    • Modified bluecloth install instructions

Goals

  • Passwords never in the clear
  • Automated deployment via Capistrano
  • A responsive web application

Todo

  • user login passwords in server logs
  • database.yml in svn contains database password
  • first few connections after deployment get "rails application failed to start" (this could be due to one of the dispatch.fcgi hacks since it wasn't fully tested, see next bullet)
  • shared hosts are not robust/stable, but this should work OK for modest needs. This recipe needs to be tested test testified.
  • when dreamhost supports mongrel, we use that instead. fcgi stinks.

Ready? Go!

SSH Setup

Capistrano enjoys using SSH to connect to your server. SVN also can be forced to enjoy connections via SSH. Let's have another quick bulleted list look at what's going to be happening.

  1. You type "ssh example.com" and it connects without a password to your server
  2. You're logged into your server (from above), and you type "ssh example.com" and logs you in again. This may seem redundant, but this is what capistrano will do on the server-side when exporting your latest release for deployment.

Let's set these up right now.

Generate a set of Fairly Insecure Passphrase-less Public/Private SSH Keys

$ ssh-keygen -d
Generating public/private dsa key pair.
Enter file in which to save the key (/Users/joeuser/.ssh/id_dsa): 
Created directory '/Users/joeuser/.ssh'.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /Users/joeuser/.ssh/id_dsa.
Your public key has been saved in /Users/joeuser/.ssh/id_dsa.pub.
The key fingerprint is:
XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX joeuser@client.example.com

Copy the public key to your remote server's authorized_keys file

$ scp .ssh/id_dsa.pub joeuser@example.com:  # Don't forget about the semicolon
$ ssh joeuser@example.com
Password: XXXXX
example.com $ mkdir .ssh
example.com $ cat id_dsa.pub >> .ssh/authorized_keys  # Used instead of copy, JIC you have other auth keys
example.com $ chmod -R go-rwx .ssh  # This protects the files from other users
example.com $ exit
Connection to example.com closed.

$ ssh joeuser@example.com   # If it works, you will not be prompted for a password
example.com $ 

On the *remote server*, let's create that "redundant" public/private keys

example.com $ ssh-keygen -d
example.com $ cat .ssh/id_dsa.pub >> .ssh/authorized_keys
example.com $ ssh joeuser@example.com
example.com $ exit
Connection to example.com closed.

example.com $ exit
Connection to example.com closed.

$  # This shell should be your client machine

Dreamhost Panel

Login to your panel and create the following Fully Hosted domain.

  • Check "FastCGI Support"
  • Web Directory: /home/username/sites/example.com/current/public

Under Goodies --> Manage MySQL

  • Create a pleasing to the eye username, and an ugly to the eye password

Once this is done cooking, the web directory tree will exist. In order for Capistrano to work correctly, you must remove the "current" directory.

$ ssh example.com
example.com $ cd sites/example.com
example.com $ rmdir current/public
example.com $ rmdir current
example.com $ exit

SVN Setup

We will not be creating a SVN repository from the Dreamhost account. Instead, we will access it via SSH using your dreamhost account. Create the SVN repository on the remote filesystem with the following commands.

$ ssh example.com
example.com $ mkdir -p svn/example   # 
example.com $ svnadmin create svn/example
example.com $ exit

Local SVN Bootstrap

$ REPO=svn+ssh://example.com/home/joeuser/svn/example
$ mkdir example
$ mkdir example/trunk example/tags example/branches
$ svn import -m="Initial project layout" example $REPO

Disassemble, reassemble!

$ rm -rf example
$ svn co $REPO example

Radiant Code

$ gem install --include-dependencies radiant
$ radiant -u example
$ cd example
$ rake tmp:create

TODO: If using Radiant 0.50 you must copy over a public/.htaccess file from another rails project. Type "rails foo" to generate a temporary directory where you may harvest one from.

Create a database.yml file similar to this

development:
  adapter: sqlite3
  database: db/radiant_dev

test:
  adapter: sqlite3
  database: db/radiant_test

production:
  adapter: mysql
  database: example_production
  username: example
  password: XXXXXXXX
  host: mysql.example.com

Remove the other database files in config

$ rm config/database.*.yml

SVN Setup

There are probably errors here, which I'll fix and announce to the list when they're made. If you edit this, please announce to the list.

$ gem install svn_conf_generator
$ script/generate svn_conf
$ rake svn:add
$ rake svn:configure
$ svn propset svn:executable "*" `find script -type f | grep -v '.svn'` public/dispatch.*
$ svn commit --message="New Rails/Radiant Project"

Now we add the dependencies to the project, paste text below

$ svn propedit svn:externals vendor

Paste the following in the command above

rails http://dev.rubyonrails.org/svn/rails/tags/rel_1-1-2
radius svn://rubyforge.org//var/svn/radius/tags/rel_0-5-1/radius
redcloth http://code.whytheluckystiff.net/svn/redcloth/tags/RELEASE_3_0_4

Save. Now run this command to download the dependencies

$ svn update vendor

You will also need bluecloth because of a bug(?), but it's an easy install. The bluecloth.rb file goes in your RAILS_ROOT directory (above vendor, config, etc.).

$ curl -O http://www.deveiate.org/code/BlueCloth-1.0.0.tar.gz || wget http://www.deveiate.org/code/BlueCloth-1.0.0.tar.gz (alternative)
$ tar -zxf BlueCloth-1.0.0.tar.gz
$ cp BlueCloth-1.0.0/lib/bluecloth.rb .
$ rm -rf BlueCloth-1.0.0

Remember, whenever you add lots of files to your project, you must add to svn (and commit, and deploy).

$ rake svn:add
$ svn commit

Application Server Setup

The next two sections comes from http://wiki.dreamhost.com/index.php/Capistrano. I whole-heartedly recommend that you read through the linked document, then compare to what I'm doing here. You will learn something, and probably teach me something along the way.

Uncomment in config/environment.rb

ENV['RAILS_ENV'] ||= 'production'

Modify public/dispatch.* shebang to

#!/usr/bin/env ruby

Modify public/.htaccess, add the "f" to fcgi below

RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]

public/.htaccess continued... Paste in the following after "RewriteEngine? On"

RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
RewriteCond %{SCRIPT_FILENAME} !maintenance.html
RewriteRule ^.*$ /system/maintenance.html [L]

There are obviously hacks (to public/dispatcher.fcgi), but it's supposed to fix the "Error 500" problems.

RailsFCGIHandler.process! nil, 10

class RailsFCGIHandler
  private
    def frao_handler(signal)
      dispatcher_log :info, "asked to terminate immediately"
      dispatcher_log :info, "frao handler working its magic!"
      restart_handler(signal)
    end
    alias_method :exit_now_handler, :frao_handler
end

Capistrano Deployment Configuration

$ cap --apply-to .
$ rake svn:add

Here is what my deploy.rb looks like when everything is said/done.

set :application, "example"
set :repository, "svn+ssh://example.com/home/joeuser/svn/#{application}/trunk"

role :web, "example.com"
role :app, "example.com"
role :db,  "example.com", :primary => true

set :deploy_to, "/home/caylan/sites/example.com"
set :use_sudo, false
set :checkout, "export"

# Basic radiant install
desc "Setup basic radiant installation"
task :radiant_big_red_button do
  run "#{current_path}/script/setup_database -o -t #{current_path}/db/templates/styled-blog.yml production";
end

# Reaper
desc "Reaper restart for dreamhost"
task :restart do
  run "#{current_path}/script/process/reaper -d dispatch.fcgi"
end

I DARE YOU TO HIT THE BIG RED BUTTON

$ svn commit -m "Primed the machine with new configuration."
$ cap radiant_big_red_button
$ cap deploy

Speed Things Up

Your fastcgi processes will be paged out of memory after a period of time. That is, unless you apply this hack...

$ ssh joeuser@example.com
example.com $ crontab -e

A magic editor opens, insert the following

*/5 * * * * lynx --source example.com > /dev/null

Parting Words

If you have any questions, or want to beef this up with some rake tasks, let me know at caylan at mac dot com.

See also: Cloxacillin, Epidermolysis, Evac, Feosol, Leishmaniasis, Light, Peacock, Phosphorus, Traps, Treacher, University, Actos, Aqueous, Biotin, Breanna, Cheetah, Dane, Elective, Lockwood, Metz, Ockelbo, Taryn, Aoc, Arcam, Family, Fujitsu, Iiyama, Janome, Mac audio, Maytag, Panasonic, Singer, Ves, Americaine, Angelman, Asthenia, Counting crow, Ferrous, Fontaine, Grateful dead, Julie, Occupational, Spironolactone, Terconazole, Aberrant, Basilar, Cca, Dissecting, Hansa, Hydatidiform, Jaeger lecoultre, Lalique, Meningocele, Mls, Sex position, Arc, Banks, Calderon, Catherine, Creative, Eclampsia, Epstein, Late, Quentin, Selective, Van, Aids, Alyson, Emilee, Friday, Giardiasis, Jessica alba, Kuma, Luna, Mifeprex, Mitchell, Topless, Benq, Karcher, Kenwood, Lanzar, Loewe, Mcintosh, Parasound, Pfaff, Supra, Aiptek, Brandt, Electrolux, Family, Fujifilm, Jumper, Kaiser, Lada, Msi, Phase linear, Adan, Alternative, Barley, Benicar, Continuous, Dwarfism, Lortab, Minolta, Nickelback, Stratton, Tagheuer watch, Alvin, Baycol, Binatone, Brenden, Cortical, Ferrlecit, Hair loss, Luis, Metrogel, Spl, Sucralfate, Airway us, Bath towels, Blaupunkt, Dermatofibroma, Jumper, Lance, Lukas, Posterior, Rebekah, Video, Videos, Adagio, Blaupunkt, Direc,Dls, Enol, Family, Genesis, Mark levinson, Minolta, Sven, Ufo, Delphin, Helix, Huawei, Jaguar, Krome, Mb quart, Profi, Rto, Sim2, Tdk, Zauber, Apacer, Canton, Jaguar, Kyocera, Nexx, Rainford, Rotel, Sim2, Singer, Vibe, Vifa.
See also: Hutchinson, Jeune, Labrador, Lee, Lenoxx, Mark levinson, Osteoarthritis, Portuguese, Rhogam, Rozerem, Scandium, Actos, Baughman, Binatone, Carly, Citizen, Electronics, Interferon, Kettler, Licon, Meckel, Morillo, Benq, Elna, General electric, Iiyama, Jaguar, Juki, Lightning audio, Necchi, Ultrasone, Umax, United, Afghanistan, Arabia, Austria, Bahrain, Belgium, Bolivia, Brunei, Cameroon, Colombia, Cuba, Czech, Dominica, Greece, Guatemala, Honduras, Iraq, Israel, Kenya, Madagascar, Micronesia, Monaco, Nigeria, Norway, Palestine, Samoa, Senegal, Switzerland, Thailand, Trinidad, Zambia, Acoustic, Aluminium, Angioedema, Asparagus, Beko, Carbonic, Craniosynostosis, Crest, Darvon, Duratuss, Fibroma, Glomerulonephritis, Gonococcal, Haiti, His, Left, Lower, Nathan, Oro, Piercing, Provera, Quinoa, Sandisk, Schweitzer, Shoes, Soundstream, Strong, Swiss knife, Vertical, Waldenstrom, Warts, Wrinkly, Y, Airis, Beko, Boston acoustics, Hifonics, Infinity, Iriver, Koss, Mcintosh, Pfaff, Zanussi, Canyon, Dragster, Krell, Lexicon, Lg, Mcintosh, New home, Orient, Praktica, Sagem, Toshiba, Belarus, Cervical, Elidel, Elliot, Glycerin, Leprosy, Locoid, Myelitis, Oxaprozin, Pantech, The stroke, Coarse, Furlong, Galloway, Gilmore girl, Guyana, Hand, Jillian, Kaden, Resonance, Rubinstein, Spica, Apacer, Arcam, Audiolab, Challenger, Fusion, Genesis, Grundig, Helix, Iiyama, Kicx, Vigor, Bernina, Denon, Dls, Koss, Lenoxx, Mtx, Onida, Oregon scientific, Senao, Sony, Umax, Adagio, Exposure, Lada, Lg, Magnat, Miele, Nrg, Premier, Quad, Siemens, Zetta, Bazooka, Iiyama, Jensen, Lenoxx, Mtx, Necchi, Profi, Tdk, Ves, Vifa, Zalman, Aerius, Colton, Dorian, Dostinex, Gitelman, Nair, Nathan, Samuel, Stomach, Tool, Zales.