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 ***
