Rails setup on Virtual/Private Server

Posted by chris olsen on December 30, 2007

A while back I posted about how to set rails up on Linode. While the instructions were in the right direction, I notices a couple of errors when I was deploying a recent application. Instead of updating the previous post I figured I would start from scratch, but this time leave out a lot of the small talk and just get down and dirty. The previous article, as well as this one, may seem to be specific to Linode, but the steps should be applicable to any virtual or private server.

This is written assuming that you have already signed up to Linode and have installed the Debian 4.0 distro as well as have your project stored within a subversion repository.

SSH into the server.

#linode machine
ssh root@ip_address

Server

Once logged into the server we need to ensure everything is up to date.

apt-get update
apt-get upgrade

Install some of the required libraries.

apt-get install ruby1.8-dev ruby ri irb rdoc libmysql-ruby mysql-server nginx subversion build-essential libopenssl-ruby1.8 sudo

Install RubyGems (Check here for the most recent release, although at the time of this writing 1.0.1 was out, but was causing me problems. I ended up sticking with v0.9.4).

wget http://rubyforge.org/frs/download.php/20989/rubygems-0.9.4.tgz
tar xzvf rubygems-0.9.4.tgz
cd rubygems-0.9.4
ruby setup.rb

Install Rails without all the documentation, it will save you a lot of time.

gem install rails --include-dependencies --no-rdoc --no-ri

Install mongrel and the mongrel cluster.

gem install mongrel -y
gem install mongrel_cluster -y

Create a new user (mongrel) that will be used to deploy the app.

useradd -m mongrel
su - mongrel
mkdir ~/.ssh
chmod 700 ~/.ssh
cat /root/my_id >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

Grant permissions to the user, mongrel.

export VISUAL=vi
visudo

Add the following line just under where root is granted all permissions.

mongrel ALL=(ALL) NOPASSWD:ALL

Create the folder where the project will be deployed to and set the proper permissions.

mkdir /var/rails
chown root:mongrel /var/rails
chmod g+w /var/rails

Local Machine

If you don’ have an ssh key created perform the following:

ssh-keygen -t rsa
scp ~/.ssh/id_rsa.pub root@ip_address:~/my_id

Ensure Capistrano is installed.

cap --version

If you see some output like $ Capistrano v2.x.x then all is good if not run the following:

sudo gem install capistrano -y

Capify your project.

cd folder_of_your_project
capify .

Next we need to create a file named “spin” in the script folder.

vi script/spin

Then insert the following and save it

mongrel_rails cluster::start

Let’s now add the new file to the project and give make it executable.

svn add config/spin
svn propset svn:executable on config/spin

Before deploying we have to make sure that the repository is fully up to date, so let’s check on the status, add any files that files that have not yet been added, then commit.

svn status
svn add
svn ci -m "Ready to deploy"

There are some changes that have to be made to the nginx.conf file and Bryan was kind enough to provide them to us. The following code also contains updates that I inserted. I will talk more about these later.

set :application, "my_app_name"

set :deploy_to, "/var/rails/#{application}"
set :user, "mongrel"
set :runner, "mongrel"

role :app, "liXX-XXX.members.linode.com"
role :web, "liXX-XXX.members.linode.com"
role :db,  "liXX-XXX.members.linode.com", :primary => true

set :svn_user, ENV['svn_user'] || "your_svn_user_name"
set :svn_password, Proc.new { Capistrano::CLI.password_prompt('SVN Password: ') }
set :repository,
Proc.new { "--username #{svn_user} " +
           "--password #{svn_password} " +
           "--no-auth-cache " +
           "http://your_svn_repos_project_path" }

namespace :init do
  desc "create database.yml"
  task :database_yml do
    set :db_user, Capistrano::CLI.ui.ask("database user: ")
    set :db_pass, Capistrano::CLI.password_prompt("database password: ")
    database_configuration =<<-EOF
---
login: &login
  adapter: mysql
  database: #{application}
  host: localhost
  username: #{db_user}
  password: #{db_pass}

production:
  <<: *login

EOF

    run "mkdir -p #{shared_path}/config"
    put database_configuration, "#{shared_path}/config/database.yml"
  end

  desc "create mongrel_cluster.yml"
  task :mongrel_cluster_yml do
    mongrel_cluster_configuration = <<-EOF
---
user: mongrel
cwd: #{current_path}
log_file: #{current_path}/log/mongrel.log
port: "8000"
environment: production
group: mongrel
address: 127.0.0.1
pid_file: #{current_path}/tmp/pids/mongrel.pid
servers: 3
EOF

    run "mkdir -p #{shared_path}/config"
    put mongrel_cluster_configuration, "#{shared_path}/config/mongrel_cluster.yml"
  end
end

namespace :localize do
  desc "copy shared configurations to current"
  task :copy_shared_configurations, :roles => [:app] do
    %w[mongrel_cluster.yml database.yml].each do |f|
      run "ln -nsf #{shared_path}/config/#{f} #{current_path}/config/#{f}"
    end
  end

  desc "Sync the uploads to the new current project"
  task :sync_uploads, :roles => [:app] do
    run "mkdir -p #{shared_path}/uploads"
    run "chown #{user}:#{user} #{shared_path}/uploads/"
    run "ln -nsf #{shared_path}/uploads #{current_path}/public/uploads"
  end
end

namespace :deploy do
  desc "stop mongrel cluster"
  task :stop do
    run "cd #{current_path};mongrel_rails cluster::stop"
  end

  desc "restart mongrel cluster"
  task :restart do
    run "cd #{current_path};mongrel_rails cluster::restart"
  end

end

after "deploy:setup", "init:database_yml"
after "deploy:setup", "init:mongrel_cluster_yml"
after "deploy:symlink", "localize:copy_shared_configurations"
after "deploy:symlink", "localize:sync_uploads"

There are some changes that have to be made to the nginx.conf file and Brian was kind enough to provide them to us. To make things easier for every one the nginx.conf settings are shown below.

user mongrel mongrel;
worker_processes 6;

error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;

events {
	worker_connections 1024;
}

http {
	include /etc/nginx/mime.types;
	default_type application/octect-stream;

	log_format main '$remote_addr - $remote_user [$time_local] $request '
	'"$status" $body_bytes_sent "$http_referer" '
	'"$http_user_agent" "$http_x_forwarded_for"';

	access_log /var/log/nginx/access.log;

	sendfile on;
	tcp_nopush on;
	tcp_nodelay off;
	gzip on;
	gzip_http_version 1.0;
	gzip_comp_level 2;
	gzip_proxied any;
	gzip_types text/plain text/html text/css application/x-javascript text/xml application.xml application/xml+rss text/javascript;

	# define your mongrel cluster here
	upstream mongrel_cluster {
		server 127.0.0.1:8000;
		server 127.0.0.1:8001;
		server 127.0.0.1:8002;
	}

	server {
		listen 80;
		client_max_body_size 100M;
		root /var/rails/YOUR_APPLICATION_NAME_GOES_HERE/current/public;
		access_log /var/log/nginx/commuitycms.access.log main;

		if (-f $document_root/system/maintenance.html) {
			rewrite  ^(.*)$  /system/maintenance.html last;
			break;
		}

		location / {
			proxy_set_header X-Real-IP $remote_addr;
			proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
			proxy_set_header Host $http_host;
			proxy_redirect false;
			proxy_max_temp_file_size 0;

			if (-f $request_filename) {
				break;
			}

			if (-f $request_filename/index.html) {
				rewrite (.*) $1/index.html break;
			}

			if (-f $request_filename.html) {
				rewrite (.*) $1.html break;
			}

			if (!-f $request_filename) {
				proxy_pass http://mongrel_cluster;
				break;
			}
		}

		error_page 500 502 503 504 /500.html;
    location = /500.html {
      root /var/rails/tissue_bank_trunk;
    }
	}
}

Copy the file up to the server from your local machine.

scp ~/Desktop/smarticast_railsrumble/nginx.conf ip_address:~/nginx.conf

Server

Then backup the original version and move the new version in to replace the previous.

su - mongrel   #unless you are already logged in as mongrel
mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.old
mv ~/nginx.conf /etc/nginx/nginx.conf

Update the nginx.conf file server { root /var/rails/project_name/current/public } value to contain the name of your project. Then restart the server.

/etc/init.d/nginx restart

While we are on the server let’s create the database.

mysqladmin -u root -p create database_name

The last thing that has to be done is to ensure that the mongrel cluster will be started if the server is rebooted.

mkdir /etc/mongrel_cluster
sudo ln -s /var/rails/my_app/current/config/mongrel_cluster.yml /etc/mongrel_cluster/my_app.yml
cp /usr/lib/ruby/gems/1.8/gems/mongrel_cluster-1.0.2/resources/mongrel_cluster /etc/init.d
sudo chmod 755 /etc/init.d/mongrel_cluster
sudo /usr/sbin/update-rc.d -f mongrel_cluster defaults

It is now time to reboot the server, and once it starts back up if the site is accessible that is a good thing. If errors are being thrown the best place to find the problem is by checking out the log files on the server.

Local

Time for the deployment.

cap deploy:setup

If all goes well on the setup let’s deploy.

deploy:cold

All code updates from here on just have to be checked into the subversion repository by one of the following:

cap deploy

That is it. Time for a celebratory beer.

Additional Notes

To retain uploaded files upon later deployments you will have to place these files in a folder that exists outside your project and create a sym link to the folder from within your project. That is what the following code is doing:

desc “Sync the uploads to the new current project”
task :sync_uploads, :roles => [:app] do
  run “mkdir -p #{shared_path}/uploads”
  run “chown #{user}:#{user} #{shared_path}/uploads/”
  run “ln -nsf #{shared_path}/uploads #{current_path}/public/uploads”
end
...
after “deploy:symlink”, “localize:sync_uploads”

*** Read update below before installing RMagick ***
Many of you will also need an image library installed to allow you resize images on upload. Although there are a few libraries out there I have had the most success with RMagick. Some people may argue that it is a little bloated, but I can always get it to work. If I ever write an amazing web app that has so much traffic that the only way I can keep the site alive is to use a lighter library then I will do exactly that, but until then I will stick with RMagick.

To install it you will need to ensure that you have a number of required libraries installed first.

sudo apt-get install build-essential libc6-dev libmagick9 libmagick++9-dev libmagic9-dev

Then install RMagick

sudo gem install Rmagick

*** UPDATE ***
I take back what I said about successfully installing an image library. Mini-Magick is super easy to install and is carries a much lighter load. I was a little amazed, and happy, at the amount of memory that was freed up.

Install it with the following

sudo gem install mini_magick

Now update the image processor that attachment_fu will use by adding the following as a parameter to the has_attachement call

:processor => :MiniMagick

*** END OF UPDATE ***

Trackbacks

Trackbacks are closed.

Comments

Leave a response

  1. kabari Wed, 09 Jan 2008 09:26:42 MST

    Hey Chris,
    I tried this on two separate Linodes, the first time it worked great! The second time for some reason is giving me a “403 Forbidden”. I checked to see if the directories were publicly viewable, they all are. Anything else you know of, like nginx related possibly, that might cause that?

  2. kabari Wed, 09 Jan 2008 16:11:40 MST

    Ok, figured it out! This may not be an issue, but this fixed it for me:


    mv /etc/nginx/nginx.conf /etc/nginx/nginx.old

    Not sure why, but naming it simply nginx.conf.old didn’t prevent it from getting picked up. Then just restart nginx.

    Also, there was a typo

    default_type application/octect-stream;

    should be

    default_type application/octect-stream;

    Thanks for the tut!

  3. kabari Wed, 09 Jan 2008 22:12:34 MST

    default_type application/octet-stream;

  4. chris Tue, 22 Jan 2008 11:14:20 MST

    Hey kabari,
    Thanks for pointing out the typo.

  5. John Reitano Fri, 26 Sep 2008 12:02:17 MDT

    Hi Chris,

    Thanks for creating this! I am planning to use it on this project.

    Do you have a recommendation about which application server I should set up: mongrel clusters, fastcgi, passenger, other?

    I have a client that is getting about 12000 dynamically generated page views per day, and I would like to be able to handle 20x that, or 240,000. They are currently using a table-based sessions.

    Also, any recommendations on a Rails-savvy hosting provider that does VPS and has good support. Their budget is between $100 and $300 a month. I’ve looked at Railsplayground, Slicehost and RailsMachine, but I’d like to find out if you have had good experience with one of these or another vendor.

    Thanks,
    John

  6. jDeppen Wed, 03 Dec 2008 13:18:48 MST

    I’m stuck here
    Create a new user (mongrel) that will be used to deploy the app

    cat /root/my_id >> ~/.ssh/authorized_keys

    What is my_id? Did I miss something?

  7. chris olsen Wed, 03 Dec 2008 20:58:55 MST

    jDeppen,

    That is a file containing your ssh public key. You could also just copy and paste it.

    In the article I uploaded the file:
    scp ~/.ssh/id_rsa.pub root@ip_address:~/my_id

  8. chris olsen Wed, 03 Dec 2008 21:05:06 MST

    John Reitano,

    Sorry for the late reply, but Wordpress never emails me when people post responses.

    Passenger is now probably the best bet. As for hosting I prefer Linode.com, but there are limits to the package sizes, but for the price I think it is the best one out there. I would say that 240,000 is easily handled by a Linode setup.

  9. jDeppen Thu, 04 Dec 2008 08:17:30 MST

    Oh sorry I was referring to the Mongrel section:

    useradd -m mongrel
    su - mongrel
    mkdir ~/.ssh
    chmod 700 ~/.ssh
    cat /root/my_id >> ~/.ssh/authorized_keys
    chmod 600 ~/.ssh/authorized_keys

    When I entered:
    cat /root/my_id >> ~/.ssh/authorized_keys
    I got:
    cat: /root/my_id: No such file or directory

    It was because I had not uploaded it at that point.

    ——————————————————————
    Is this a typo?
    “Next we need to create a file named “spin” in the script folder.”
    vi script/spin

    “Then insert the following and save it”
    mongrel_rails cluster::start

    “Let’s now add the new file to the project and give make it executable.”
    svn add config/spin
    svn propset svn:executable on config/spin

    Should it be:
    svn add script/spin
    svn propset svn:executable on script/spin

    ——————————————————————
    Thanks for taking the time to make this post.
    I used this video to get started too:
    http://smartic.us/2007/9/4/smarticast-4-rails-rumble-primer

    Any chance you’ll make a passenger post?

Comments