Friday, August 14, 2009

Deploying a Rails Application to Dreamhost Part 2

I've created an empty Demo app in Dreamhost. I have a working Rails app locally. I need it working at Dreamhost remotely. I need a fast edit-and-deploy process. I need backups.

Assumptions, Versions:

I overload the term "Dreamhost" here to also denote the Dreamhost server that hosts all my domains and servers, including mikedll.com, demo.mikedll.com, and the hypothetical projectname.mikedll.com.

A MySQL deployment database has been created via Dreamhosts' Web Panel. Rails knows its contact info.

My demo app has been launched by Dreamhost using FastCGI, so I'll use FastCGI here (as oppose to Passenger).

Files like config/environment.rb, the logs/ directory, and the vendor/ directory don't need to be synced, but most other files do. A version control system can help with this. Beyond synchronization, it will help me recover when I break a feature. SCP and rsync won't do this. I'll use Git. Hidden Reflex says Git is better than Mercurial and compatible with Trac. Git has Github, which makes it trendy. Git also supports tiered revision, which Subversion doesn't. I've been used to tiered revisions since using AccuRev.

Syncplicity, which I've been using for nine months, will be used for semi-automatic backups.

Local and remote software versions compare as follows:

  • Operating System: Local Ubuntu 8.04.3 LTS (Hardy Heron) vs Dreamhost some variant of Linux 2.6.24
  • Ruby: Local 1.8.6 vs Dreamhost 1.8.5
  • Rails: Local 2.0.2 vs Dreamhost 2.2.2
  • RubyGems: Local 0.9 vs Dreamhost 1.3.1

My Ubuntu package manager is behind on versions for both RubyGems and Rails. Besides this problem, most of the community uses RubyGems for package management, not a default package manager.

Given that I'm on Rails 2.0.2 and Dreamhost is on 2.2.2, I anticipate version conflicts. I've encountered a similar situation before.

Develop a Plan

To avoid the version conflicts, I'll sync Rails versions. It'll be an upgrade for me.

I'll manually install RubyGems and use it to install Rails.

I will continue to use Ubuntu's package manager for non-ruby dependencies.

After that, I'll learn Git via the Git tutorial, and then put it in charge of my app.

I can test my edit-and-deploy process by making changes and then expecting to observe them on Dreamhost.

Carrying Out The Plan

I downloaded the following packages and gems into my ~/settings/packages directory.

I put RubyGems in charge.

# Remove Ubuntu's versions
sudo aptitude remove rails
sudo aptitude remove rubygems

# Install RubyGems.
# The only surprise here is that I have to create a symlink.
tar -xzf ./rubygems-1.3.5.tgz.tar -C ~/
cd ~/rubygems-1.3.5
sudo ruby setup.rb
sudo ln -s /usr/bin/gem1.8 /usr/bin/gem

I then installed some dependencies with Ubuntu. The ruby 1.8 development files may be required by the sqlite3-ruby gem. The sqlite3 development headers are definitely required. Without them, the sqlite3-ruby gem gives a "missing sqlite3.h" build error.

# Following above explanations, get sqlite3-ruby dependencies
sudo aptitude install ruby1.8-dev
sudo aptitude install libsqlite3-dev

While installing gems. I explicitly specified the Dreamhost versions. RubyGems recognized and used the downloaded .gem files, which were in the current working directory.

sudo gem install -V sqlite3-ruby --version 1.2.5
sudo gem install -V rails --version 2.2.2
cd ~/myapp
rake rails:update  # update my app's files to 2.2.2

Here, I deleted deprecated calls to cache_template_extensions= and cache_template_loading= in some config/environments/*.rb files.

I then learned and setup Git.

# On Dreamhost, create a remote repository:
mkdir -p ~/git/projectname
cd ~/git/projectname
git init

# On local laptop:
cd projectname # This is folder of my existing app that is not yet versioned
git init
git add .
git commit -m "First revisioned copy of my code."

# Still on local laptop, configure it as a clone the remote repository.
# The remote repository will then be "upstream" of this local one:
git remote add origin mrmike@mikedll.com:git/projectname # Name remote repo 'origin'
git config branch.master.remote origin               # Track remote 'origin'
git config branch.master.merge master                # ... the master branch
git push                                             # Inform 'origin' of commit

# Back to Dreamhost, we create a deployed working copy:
git clone ~/git/projectname ~/projectname.mikedll.com
cd ~/projectname.mikedll.com
git pull

My code was in deployment position. I reviewed my demo app experience to fix file permissions and configure FastCGI. I reproduce a couple steps from that experience here:

# Added this to my .bash_profile, for running rake commands
export RAILS_ENV='production' 

# Initialize the remote mysql database. Here, rails worked flawlessly.
rake db:migrate

My applicaiton should now be deployed and under version control.

Review the solution

I test my solution by editing files locally, commiting them with Git, logging into mikedll.com and pulling the changes, and viewing the website at its public URL. I expect to see my changes.

Then I test my backups by pulling changes into the Windows working copy of the central repository, letting Syncplicity see the changes and back them up to its servers, and viewing my backed up files through the Flash interface at the Syncplicity website. I expect to see the changes reflected in those files.

These tests passed. I had successfully deployed my web app to Dreamhost.

I could also use Git to send changes from Dreamhost to my laptop. This will be used when bugs are fixed on the remote production server.

git commit by definition doesn't require a password like Subversion would, but git push and git pull do. I setup private/public SSH keys to simplify this edit-and-deploy process further.

I used this result to converting my existing static website, mikedll.com, into an identically setup Rails/Git/Dreamhost configuration.

A struggle while carrying out the plan was that, due to extremely poor internet at the time, I had to manually fetch certain gems including actionpack, activesupport, activerecord, actionmailer, and activeresource. RubyGems kept timing out when trying to download them. I omitted these downloads from the above list because if my internet had been better, RubyGems would've installed them for me.

A failure while carrying out the plan was that I tried to do some things with Git that may not have made sense. Originally, I had hoped to avoid a central repository, and to instead push updates from my local laptop repository to remote and backup repositories. I couldn't get this to work, though. I might have been needlessly running from a centralized setup for the sake of being different, instead of trying to get things done.

Future and Related Work

Heroku offers special Rails hosting such that your edit-and-deploy process can consist of a single rake command.

Dealing with conflicts in Rails versions is a known problem and a popular solution is to freeze rails.

The user experiences that alerted me to the deprecations of cache_template_extensions= and cache_template_loading= were Paul Sturgess' snippets collection and the Morph Labs website, respectively.

A Stackoverflow entry showed me how to configure an existing repository as if it were created with the clone command. This avoids the dirty method of cloning a new local repository and deleting the existing repository, which I had done at first and felt embarrassingly weaksauce.

My local laptop environment is actually Ubuntu on VirtualBox, but Syncplicity only works on Windows. So, I created another windows working copy off the central one just for Syncplicity. It will be my (manual) responsibility to pull updates to this copy when I want a backup that is neither on Dreamhost's hard drives nor my own.

My edit-and-deploy process is still missing some vital steps for it to scale, but it will suffice for now. Months from now as bugs begin to pile up, how will I manage them? How will I integrate tickets and milestones? With Trac? With Lighthouse?

Seemingly, Capistrano helps with Rails deployments and many system administration tasks where you have to login to many servers, but I haven't examined it in any depth yet.

Dreamhost strongly recommends launching rails apps with Passenger instead of FastCGI. I will add that as a future todo for my project.

Work with NGOs

I met Anita today, and she told me about her volunteer work with NGOs. She enjoys the work that she does. Usually, she works with children who are picked up off the street or sometimes for petty theft or running away from home. Or, sometimes they just go missing from their parents.

Frequently, the children are gone within a short time span. It could me a few months or a few days before they leave. They are often "locked up" in small rooms where they can't really go anywhere, so they don't like it.

I am used to organization like the Boys and Girls club, where short term work is frowned upon. Here, I may be able to get in simple interactions. I look forward to following through on this.

Tuesday, August 11, 2009

Create a Rails Application on Dreamhost

  1. In the Dreamhost Web Panel, panel.dreamhost.com:

    1. Create demo.mikedll.com. Wait until the domain name is resolvable.

    2. In the host configuration, check "Enable FastCGI" and serve documents from /home/myusername/demo.mikedll.com/public.

  2. SSH into the web server and run:

    # Create the app
    rails demo.mikedll.com
    
    
    # Go into the public directory
    cd demo.mikedll.com/public
    
    
    # Recurisvely add world-executable to directories
    find . -type d -exec chmod o+x {} \;
    
    
    # Recursively add world-readable to files
    find . ! \( -type l \) ! \( -type d \) -exec chmod o+r {} \;
    
  3. In the root of the application directory, run:

    # Make dynamic-content
    ./script/generate controller demo index`.
    
    
    # Initialize the sqlite database
    rake db:migrate
    
  4. Create demo.mikedll.com/public/.htaccess with:

    # Launch dispatch.fcgi on requests for which
    # there is no static file
    AddHandler fastcgi-script fcgi
    RewriteEngine On
    RewriteBase /
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
    
  5. Remove demo.mikedll.com/public/index.html.

  6. Done

Expect visits to demo.mikedll.com to display static content.

Expect visits to demo.mikedll.com/demo to display dynamic content.

Pitfalls

  • This Apache error:

    [...] [error] [client ...] File does not exist: ... demo.mikedll.com/public/missing.html
    

    occurs if the .htaccess file is omitted.

  • Recent changes in the application code are not visible if an already-running instance of the application is not killed with killall dispatch.fcgi.

  • This Apache error:

    [...] [error] [client ...] FastCGI: comm with (dynamic) server ".../current/public/dispatch.fcgi" aborted: (first read) idle timeout (60 sec)
    [...] [error] [client ...] FastCGI: incomplete headers (0 bytes) received from server ".../current/public/dispatch.fcgi"
    

    occurs when then application crashes while loading. For example, I get this error when my config/environment.rb has a bug in it. The browser will hang for 60 seconds before the server responds with a terse, generic, rails error.

    To debug such issues, I run ./dispatch.fcgi at the command line. No arguments are required. Usually dispatch.fcgi will crashe, give an informative error, and provide faster debugging feedback.

Similar Work

More Trouble-shooting help is available in the Dreamhost "Rails Wiki".

Another guide on a Dreamhost/Rails/Radiant deployment features an application that has many users. It goes into more depth and draws on more experience.

My guide builds on another guide published early this year. This guide had some screenshots and clear directions. However, the .htaccess file in that guide doesn't serve static content, which was a problem for me. Someone else had the same problem. While solving this problem, I used sample .htaccess files at The Rails Playground and this Snipplr entry.

Related Technology

Passenger is a deployment configuration that is different from FastCGI. Dreamhost supports it, but I haven't gotten it to work yet.

Capistrano has something to do with automating common server tasks, like application deployment.