Ruby on Rails deployment
From Smith family
Various different ways to deploy a Ruby on Rails application. Assumes the server is set up and the databases are created.
Bare bones deployment: Apache2 and mod_ruby, application at root of virtual host
This is a very basic installation simply to check that Rails is running properly. See some config files for Apache and some more Apache2 config files.
- Create a directory to contain the application
root@server:~# mkdir /var/www/project.domain.tld root@server:~# cd /var/www/project.domain.tld
- Download the Rails app from the Suvbersion repository
root@server:/var/www/project.domain.tld# svn checkout svn://svn.server.tld/path/to/app/
- Note that this will insert the last directory into the checked-out path, so the Rails root will be in
/var/www/project.domain.tld/app/
- Update
config/database.yml
production: adapter: mysql encoding: utf8 database: project_production pool: 5 username: project password: SecretPassword socket: /var/run/mysqld/mysqld.sock
- Uncomment this line in
config/environment.yml
ENV['RAILS_ENV'] ||= 'production'
- Create the database and its user
root@server:~# mysql -u root -p mysql> create database project_production; mysql> create user 'project'@'localhost' identified by 'SecretPassword'; mysql> grant all on project_production.* to 'project'@'localhost';
- Run the database migration to create the tables and populate them
root@server:/var/www/project.domain.tld# rake db:migrate RAILS_ENV=production
- Create the Apache2 configuration file,
/etc/apache2/sites-available/project.domain.tld
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/project./deployment-20090209/public
ServerName depot.njae.me.uk
<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>
<Directory /var/www/>
Options FollowSymLinks MultiViews
AllowOverride None
Order allow,deny
allow from all
</Directory>
<Directory /var/www/depot.njae.me.uk/deployment-20090209/public>
# General Apache options
Options +FollowSymLinks +ExecCGI
AllowOverride None
Order allow,deny
allow from all
#AddHandler fastcgi-script .fcgi
AddHandler cgi-script .cgi
</Directory>
RewriteEngine On
# RewriteRule ^$ index.html [QSA]
RewriteRule ^([^.]+)$ $1.html [QSA]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ dispatch.cgi [QSA,L]
# In case Rails experiences terminal errors
ErrorDocument 500 "<h2>Application error</h2>Rails application failed to start properly"
# this not only blocks access to .svn directories, but makes it appear
# as though they aren't even there, not just that they are forbidden
<DirectoryMatch "^/.*/\.svn/">
ErrorDocument 403 /404.html
Order allow,deny
Deny from all
Satisfy All
</DirectoryMatch>
ErrorLog /var/log/apache2/error.log
LogLevel warn
CustomLog /var/log/apache2/access.log combined
ServerSignature On
</VirtualHost>
- Enable the site and reload it into Apache
root@server:~# a2ensite project.domain.tld root@server:~# /etc/init.d/apache2 reload
Initial deployment: Capistrano, Apache2, mod_ruby, application at root of virtual host
Source instructions, Git + Passenger
This assumes the app will be deployed to the root of a virtual host all of its own.
- Copy the project to a stable branch, for deployment.
user@desktop:~/project$ svn copy -m "Creating stable branch" http://svn.domain.tld/svn/repo/project/trunk http://svn.domain.tld/svn/repo/project/branches/stable
- (note the absence of trailing '/' characters in the SVN command)
- Capify the project:
user@desktop:~/project$ capify .
- Modify
project/config/deploy.rb:
set :application, "project"
set :repository, "http://svn.domain.tld/svn/repo/#{application}/branches/stable"
set :deploy_to, "/var/www/project.domain.tld/"
set :scm, :subversion
set :scm_username, 'developer_user'
role :app, "project.domain.tld"
role :web, "project.domain.tld"
role :db, "project.domain.tld", :primary => true
# Copy the database.yml file across, as it's not kept in the SVN repository
after "deploy:update_code" , :configure_database
desc "copy database.yml into the current release path"
task :configure_database, :roles => :app do
db_config = "#{deploy_to}/config/database.yml"
run "cp #{db_config} #{release_path}/config/database.yml"
end
# As we're not running any other server processes (FastCGI, Mongrel, etc.),
# we don't need to start and stop them.
namespace :deploy do
[:start, :stop, :restart].each do |t|
desc "#{t} task is a no-op without other server processes"
task t, :roles => :app do ; end
end
end
- Note that we instruct Capistrano to copy
database.ymlinto the right place, as it's not in the repository. Also, as we're not running any other server processes (FastCGI, Mongrel, etc.), we don't need to start and stop them.
- Create the production database user's password and store it in the
productionstanza indatabase.yml - On the server, create the
/var/www/project.domain.tlddirectory, and make it writeable by the user:
root@server:~# mkdir /var/www/project.domain.tld root@server:~# chown -R user:user /var/www/project.domain.tld
- Copy across the
database.ymlfile
user@desktop:~/project$ ssh user@server 'mkdir /var/www/project.domain.tld/config'
user@desktop:~/project$ scp config/database.yml \
user@server:/var/www/project.domain.tld/config/database.yml
- Create the production database on the server
root@server:~# mysql -u root -p mysql> create database project_production; mysql> grant all on project_production.* to 'project'@'localhost' identified by 'password'; mysql> quit;
- On the server, create the
/etc/apache2/sites-available/project.domain.tldfile:
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/project.domain.tld/current/public
ServerName project.domain.tld
SetEnv RAILS_ENV production
<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>
<Directory /var/www/>
Options FollowSymLinks MultiViews
AllowOverride None
Order allow,deny
allow from all
</Directory>
<Directory /var/www/project.domain.tld/current/public>
AllowOverride None
Order allow,deny
allow from all
# General Apache options
#AddHandler fastcgi-script .fcgi
AddHandler cgi-script .cgi
#AddHandler fcgid-script .fcgi
Options +FollowSymLinks +ExecCGI
</Directory>
RewriteEngine On
# If the maintenance page exists, rewrite all requests to that page
RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
RewriteCond %{SCRIPT_FILENAME} !maintenance.html
RewriteRule ^.*$ /system/maintenance.html [L]
RewriteRule ^([^.]+)$ $1.html [QSA]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ dispatch.cgi [QSA,L]
# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
ErrorDocument 500 "<h2>Application error</h2>Rails application failed to start properly"
# this not only blocks access to .svn directories, but makes it appear
# as though they aren't even there, not just that they are forbidden
<DirectoryMatch "^/.*/\.svn/">
ErrorDocument 403 /404.html
Order allow,deny
Deny from all
Satisfy All
</DirectoryMatch>
ErrorLog /var/log/apache2/error.log
# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
LogLevel warn
CustomLog /var/log/apache2/access.log combined
ServerSignature On
</VirtualHost>
- Note the
SetEnvdirective to ensure this is a production system.
- Set up and check the deployment
user@desktop:~/project$ cap deploy:setup user@desktop:~/project$ cap deploy:check
- Fix any reported errors
- Enable the site and restart Apache
root@server:~# /etc/init.d/a2ensite project.domain.tld root@server:~# /etc/init.d/apache2 reload
- Make the first deployment
user@desktop:~/project$ cap deploy:migirations
Apache2 and Mongrel cluster, deployed with Capistrano, application at root of virtual host
- Ensure Mongrel cluster is installed on the server.
Get Mongrel running
- For testing, open ports in the firewall to allow direct access to the Mongrel cluster. Modify
/etc/iptables.rulesto include these lines near the end:
## Temporarily allow Mongrel clusters across the LAN iptables -A INPUT -i $IFACE -p tcp -s $LAN --dport 3000 -j ACCEPT iptables -A INPUT -i $IFACE -p tcp -s $LAN --dport 3001 -j ACCEPT iptables -A INPUT -i $IFACE -p tcp -s $LAN --dport 3002 -j ACCEPT
- and restart the firewall
root@server:~# /etc/init.d/iptables restart
- Deploy the application to the server, for instance using Capistrano (as above).
- On the server,
cdto the root of the Rails application and set up the Mongrel cluster
user@server:~$ cd /var/www/project.domain.tld/current user@server:current$ mongrel_rails cluster::configure -e production -p 3000 -N 3 Writing configuration file to config/mongrel_cluster.yml.
- Modify the file
config/mongrel_cluster.yml
--- cwd : /var/www/depot.njae.me.uk/current log_file: log/mongrel.log port: "3000" environment: production pid_file: tmp/pids/mongrel.pid servers: 3 docroot: public user: bob group: bob
- the
cwd,docroot,user, andgroupelements are new. Bob is the name of a non-privileged user.
- Start the cluster
user@server:current$ mongrel_rails cluster::start starting port 3000 starting port 3001 starting port 3002
- Test the Mongrel cluster by pointing a web browser at server.domain.tld:3000, server.domain.tld:3001, and server.domain.tld:3002
Make Apache a proxy for Mongrel
Now to configure Apache to act as a load-balancing proxy
- Enable the modules Apache will need
root@server:~# a2enmod proxy_balancer root@server:~# a2enmod proxy_http root@server:~# a2enmod rewrite
- Edit the
/etc/apache2/sites-enabled/project.domain.tldfile
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/project.domain.tld/current/public
ServerName project.domain.tld
SetEnv RAILS_ENV production
<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>
<Directory /var/www/>
Options FollowSymLinks MultiViews
AllowOverride None
Order allow,deny
allow from all
</Directory>
<Directory /var/www/depot.njae.me.uk/current/public>
# General Apache options
Options +FollowSymLinks +ExecCGI
AllowOverride None
Order allow,deny
allow from all
</Directory>
<Proxy *>
Order allow,deny
Allow from all
</Proxy>
<Proxy balancer://mongrel_cluster>
BalancerMember http://127.0.0.1:3000
BalancerMember http://127.0.0.1:3001
BalancerMember http://127.0.0.1:3002
</Proxy>
RewriteEngine On
# If the system maintenance page exists, serve that instead of any other page
RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
RewriteCond %{SCRIPT_FILENAME} !maintenance.html
RewriteRule ^.*$ /system/maintenance.html [L]
# Rewrite rule to check for the index page: Apache serves this directly
RewriteRule ^/$ /index.html [QSA]
# Rewrite rule for static pages: Apache serves these direclty
RewriteRule ^([^.]+)$ $1.html [QSA]
# If no other rules match, pass the request to the Mongrel cluster
# Redirect all non-static requests to cluster
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
RewriteRule ^/(.*)$ balancer://mongrel_cluster%{REQUEST_URI} [P,QSA,L]
## Deflate served pages to improve speed over the network
#AddOutputFilterByType DEFLATE text/html text/plain text/xml application/xml application/xhtml+xml text/javascript text/css
#BrowserMatch ^Mozilla/4 gzip-only-text/html
#BrowserMatch ^Mozilla/4\.0[678] no-gzip
#BrowserMatch \\bMSIE !no-gzip !gzip-only-text/html
## Uncomment for deflate debugging
#DeflateFilterNote Input input_info
#DeflateFilterNote Output output_info
#DeflateFilterNote Ratio ratio_info
#LogFormat '"%r" %{output_info}n/%{input_info}n (%{ratio_info}n%%)' deflate
#CustomLog /var/log/apache2/project_deflate.log deflate
# In case Rails experiences terminal errors
# Instead of displaying this message you can supply a file here which will be rendered instead
ErrorDocument 500 "<h2>Application error</h2>Rails application failed to start properly"
# this not only blocks access to .svn directories, but makes it appear
# as though they aren't even there, not just that they are forbidden
<DirectoryMatch "^/.*/\.svn/">
ErrorDocument 403 /404.html
Order allow,deny
Deny from all
Satisfy All
</DirectoryMatch>
ErrorLog /var/log/apache2/error.log
# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
LogLevel warn
CustomLog /var/log/apache2/access.log combined
ServerSignature On
</VirtualHost>
- (if you use the Deflate module, remember to enable it (
a2enmod deflate) and create an empty log file (touch /var/log/apache2/project_deflate.log) before restarting Apache.
- Reload the Apache configuration
root@server:~# /etc/init.d/apache2 force-reload
- Close the Mongrel ports to the outside world: delete these lines from
/etc/iptables.rules:
## Temporarily allow Mongrel clusters across the LAN iptables -A INPUT -i $IFACE -p tcp -s $LAN --dport 3000 -j ACCEPT iptables -A INPUT -i $IFACE -p tcp -s $LAN --dport 3001 -j ACCEPT iptables -A INPUT -i $IFACE -p tcp -s $LAN --dport 3002 -j ACCEPT
- and restart the firewall
root@server:~# /etc/init.d/iptables restart
Update the project's capfile
See the Deploying Rails Applications book for details on using Monit to start and stop Mongrel clusters. If you're running Mongrel cluster as a service outside Monit, you'll need to include some modifications to config/deploy.rb. Create the custom start, stop, and restart tasks as shown below:
# Custom tasks for starting and restarting Mongrel cluster
namespace :deploy do
desc "start the mongrel cluster"
task :start, :roles => :app do
sudo "/usr/bin/mongrel_cluster_ctl start"
end
desc "stop the mongrel cluster"
task :stop, :roles => :app do
sudo "/usr/bin/mongrel_cluster_ctl stop"
end
desc "restart the mongrel cluster"
task :restart, :roles => :app do
sudo "/usr/bin/mongrel_cluster_ctl restart"
end
end
Make Mongrel run as a service
- Create the file
/etc/mongrel/project.conf
# The user and group which run Mongrel user: bob group: bob # The location of the Rails application and the environment to run in cwd: /var/www/depot.njae.me.uk/current environment: production # The number of Mongrels in the cluster servers: 3 # The starting port port: "3000" # The IP addresses allowed to connect to Mongrel address: 127.0.0.1 # The location of the process ID files relative to the directory above pid_file: tmp/pids/mongrel.pid
- Stop the existing Mongrel cluster and restart it with this new config file
user@server:current$ mongrel_rails cluster::start user@server:current$ mongrel_cluster_ctl start
- Remove the file
config/mongrel_cluster.yml - Check you can still use the application. This checks that the config file is correct.
- Stop the Mongrel cluster:
user@server:current$ mongrel_cluster_ctl stop
- Copy the Mongrel init.d script across
root@server:~# cp /usr/lib/ruby/gems/1.8/gems/mongrel_cluster-1.0.5/resources/mongrel_cluster /etc/init.d/
- Modify the
/etc/init.d/mongrel_clusterscript to remove the reference to the non-existentmongreluser. Replace the reference to one toroot. Chanage the lines near the top of the script to look like this:
# USER=mongrel USER=root
- Set up the service and start it
root@server:~# chmod +x /etc/init.d/mongrel_cluster root@server:~# update-rc.d mongrel_cluster defaults
If you want to stop the Mongrel cluster running as a service, remove the init scripts:
root@server:~# rm /etc/init.d/mongrel_cluster root@server:~# update-rc.d mongrel_cluster remove
The Deploying Rails Applications book goes into more detail about using Monit to monitor the Mongrel cluster and restart Mongrel instances if any of them get into trouble. I've not got round to doing that yet. I will do if I decide to use Mongrel as my main production server.
Apache2 and Phusion Passenger, deployed with Capistrano, application at root of virtual host
Passenger is an application server for Rails that is run via Apache. The Passenger application server spawns a new process when it's needed and keeps it running for a while. After about 10 minutes without use, the application server process terminates to save system resources. When the next Rails request comes along, Apache spawns a new Passenger process to handle it.
The nice thing is that the configuration is just about non-existent.
This assumes the app will be deployed to the root of a virtual host all of its own.
- Copy the project to a stable branch, for deployment.
user@desktop:~/project$ svn copy -m "Creating stable branch" http://svn.domain.tld/svn/repo/project/trunk http://svn.domain.tld/svn/repo/project/branches/stable
- (note the absence of trailing '/' characters in the SVN command)
- Capify the project:
user@desktop:~/project$ capify .
- On the server, install the Passenger gem and install it:
root@desktop:~# gem install passenger root@desktop:~# passenger-install-apache2-module
- Add these lines to
/etc/apache2/httpd.conf
# Load the Passenger module for Rails applications LoadModule passenger_module /usr/lib/ruby/gems/1.8/gems/passenger-2.0.6/ext/apache2/mod_passenger.so PassengerRoot /usr/lib/ruby/gems/1.8/gems/passenger-2.0.6 PassengerRuby /usr/bin/ruby1.8
- On the server, create the /var/www/project.domain.tld directory, and make it writeable by the user:
root@server:~# mkdir /var/www/project.domain.tld root@server:~# chown -R user:user /var/www/project.domain.tld
- Note that Passenger runs as the user who owns the
config/environment.rbfile, so ensure that user has adequate rights to this bit of the file system.
- Copy across the database.yml file
user@desktop:~/project$ ssh user@server 'mkdir /var/www/project.domain.tld/config'
user@desktop:~/project$ scp config/database.yml \
user@server:/var/www/project.domain.tld/config/database.yml
- Create the production database on the server
root@server:~# mysql -u root -p mysql> create database project_production; mysql> grant all on project_production.* to 'project'@'localhost' identified by 'password'; mysql> quit;
- Create the virtual host configuration file,
/etc/apache2/sites-available/project.domain.tldcontaining just this:
<VirtualHost *:80>
ServerName project.domain.tld
DocumentRoot /var/www/project.domain.tld/current/public
RewriteEngine On
# If the system maintenance page exists, serve that instead of any other page
RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
RewriteCond %{SCRIPT_FILENAME} !maintenance.html
RewriteRule ^.*$ /system/maintenance.html [L]
# In case Rails experiences terminal errors
# Instead of displaying this message you can supply a file here which will be rendered instead
ErrorDocument 500 "<h2>Application error</h2>Rails application failed to start properly"
# this not only blocks access to .svn directories, but makes it appear
# as though they aren't even there, not just that they are forbidden
<DirectoryMatch "^/.*/\.svn/">
ErrorDocument 403 /404.html
Order allow,deny
Deny from all
Satisfy All
</DirectoryMatch>
ErrorLog /var/log/apache2/error.log
# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
LogLevel warn
CustomLog /var/log/apache2/access.log combined
ServerSignature On
</VirtualHost>
- Enable the site and restart Apache
root@server:~# /etc/init.d/a2ensite project.domain.tld root@server:~# /etc/init.d/apache2 reload
- Set up and check the deployment
user@desktop:~/project$ cap deploy:setup user@desktop:~/project$ cap deploy:check
- For deployment, you need to modify the Capistrano deployment recipe,
project/config/deploy.rb. Update the:start.:stop. and:restarttasks to be as shown:
set :application, "project"
set :repository, "http://svn.domain.tld/svn/repo/#{application}/branches/stable"
set :deploy_to, "/var/www/project.domain.tld/"
set :scm, :subversion
set :scm_username, 'developer_user'
role :app, "project.domain.tld"
role :web, "project.domain.tld"
role :db, "project.domain.tld", :primary => true
# Copy the database.yml file across, as it's not kept in the SVN repository
after "deploy:update_code" , :configure_database
desc "copy database.yml into the current release path"
task :configure_database, :roles => :app do
db_config = "#{deploy_to}/config/database.yml"
run "cp #{db_config} #{release_path}/config/database.yml"
end
namespace :deploy do
task :start, :roles => :app do
end
task :stop, :roles => :app do
end
desc "Restart Application"
task :restart, :roles => :app do
run "touch #{release_path}/tmp/restart.txt"
end
end
- Make the first deployment
user@desktop:~/project$ cap deploy:migirations
Subsequent deployments with Capistrano from a stable branch
Assume a bunch of enhancements have been made in the trunk. These need to be copied to the stable branch in Subversion and redeployed.
- Check out a working copy of the stable branch
user@desktop:~$ mkdir -p /path/to/stable/branch/working/copy user@desktop:~$ cd /path/to/stable/branch/working/copy user@desktop:copy$ svn checkout uri://repository/app/branches/stable user@desktop:copy$ cd stable
- Find the latest revision number of the stable branch
user@desktop:stable$ svn log
- note revision number of last commit. Assume its revision 30.
- Find the latest revision number of the trunk. Either look at the repository, or update the trunk and look at the log
user@desktop:trunk$ svn update user@desktop:trunk$ svn log
- Note the different directory: do this in a different console. Assume the trunk is at revision 45.
- Merge the changed from the trunk into the stable branch
user@desktop:stable$ svn merge -r 30:45 uri://repository/app/trunk
- Commit the updated stable branch to the repository
user@desktop:stable$ svn commit -m "Merged updates from trunk into stable branch"
- Redeploy the application
user@desktop:stable$ cap deploy:migrations
See also
- Rails Wiki: Apache + Mongrel
- Time For A Grown-Up Server: Rails, Mongrel, Apache, Capistrano and You
- Deploying Rails on Ubuntu Dapper
- Mongrel docs on Apache (out of date)
- Deploying Ruby on Rails on Ubuntu Feisty Fawn via Mongrel Cluster and Apache
- Passenger user guide
- How to deploy Passenger on Ubuntu
