Rails setup on Virtual/Private Server
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.

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?
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!
default_type application/octet-stream;Hey kabari,
Thanks for pointing out the typo.
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
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?
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
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.
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?