The Ultimate Server - Ruby on Rails with Lighttpd, Apache2 with PHP, MySQL, PostgreSQL on Ubuntu Dapper Server 12
As we all know, in the real world things are rarely simple, and the deployment of Ruby on Rails sites is certainly no exception to this rule. In fact it can sometimes seem that what Rails gives with its ease of development, it takes away when it comes to the quagmire of deployment. First you have to choose your server - Apache with FastCGI? Lighttpd? Apache with Mongrel cluster? The options can seem baffling to the newcomer.
Running first on a Debian platform, I initially deployed my Rails sites using Apache 1.3 with FastCGI. However, this proved so unreliable that eventually I was forced to take all my sites offline in order to protect the health of the rest of the system. Frequently I would notice sluggish performance, and log in to find a zombie ruby process hogging all available CPU. And I'm not alone.
So when I recently rebuilt my server using Ubuntu Dapper Server I decided to try LightTPD - AKA Lighty. The Mongrel cluster approach was out, as this requires Apache2.2, and I wanted to stick to the Ubuntu packages for ease of ongoing maintenance, and avoid compiling anything from source. My server also hosts a number of sites running happily on Apache and using PHP, so the new Lighty solution had to co-exist with these.
Based on my experience, I'm going to show how you too can build the ultimate server (tm) - one that can handle virtual hosting of Apache/PHP sites alongside multiple Rails sites on Lighty, where any site can use MySQL or PostgreSQL as its database, and all this can be made to coexist on one server using nothing more than an installation of RubyGems and the standard Ubuntu packages.
Assumptions and Requirements
Firstly I'm assuming your starting point is a bare install of Ubuntu Server. Don't select the LAMP option (although it might not hurt - I haven't tested it) as the following procedure will install everything you need. Secondly, I'm assuming you have more than one public IP available to your server. If you don't, you will need to run Lighty on a different port and proxy all your Rails sites through from Apache, as described in part 3 of this article, and also in more depth in this article.
Our finishing point is a multi-homed server with Lighty on one IP for Rails, Apache2 on another IP for Apache2/PHP sites, and MySQL and PostgreSQL support in Rails and PHP. I'm not covering things like Subversion and Capistrano here - that's a whole different can of worms. For a good tutorial on deploying onto Lighty using Capistrano, I highly recommend this article.
Part 1: Initial Setup and Package Installation
Pretty much everything that follows requires root priveleges, so to save your fingers some work, acquire a root shell:
james@ubuntu:~$ sudo -i Password: root@ubuntu:~#
To kick off, enable the Universe repositories in /etc/apt/sources.list - this is required for some packages such as irb. Then bring your server up to date and install build-essential, to allow building of mysql and postgres gems later:
apt-get update apt-get upgrade apt-get install build-essential
Next edit /etc/network/interfaces to configure your server with 2 IPs; this article will use 192.168.10.3 for Apache2 and 192.168.10.4 for Lighty. Obviously you should substitute your real, public IPs. Note the auto lines to ensure the interfaces come up on reboot:
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).
# The loopback network interface
auto lo
iface lo inet loopback
# The primary network interface
auto eth0
iface eth0 inet static
address 192.168.10.3
netmask 255.255.255.0
network 192.168.10.0
gateway 192.168.10.1
auto eth0:1
iface eth0:1 inet static
address 192.168.10.4
netmask 255.255.255.0
Now install Ruby:
apt-get install ruby1.8 ruby1.8-dev irb1.8 rdoc1.8
create symlinks:
ln -s /usr/bin/ruby1.8 /usr/bin/ruby ln -s /usr/bin/irb1.8 /usr/bin/irb
Install RubyGems:
wget http://rubyforge.org/frs/download.php/11289/rubygems-0.9.0.tgz tar -xzf rubygems-0.9.0.tgz cd rubygems-0.9.0 ruby setup.rb
Install Rails:
gem install rails --include-dependencies
Install PostgreSQL and postgres gem (ignore compilation errors):
apt-get install postgresql-8.1 postgresql-server-dev-8.1 libpgsql-ruby1.8 gem install postgres
Install MySQL and mysql gem (choose option 2):
apt-get install mysql-server libmysqlclient15-dev libmysql-ruby1.8 gem install mysql
Install fcgi gem (needed to use Rails with Lighty):
apt-get install libfcgi-dev libfcgi-ruby1.8 gem install fcgi
Make sure ruby is happy with everything installed:
root@ubuntu:~/rubygems-0.9.0# irb irb(main):001:0> require 'fcgi' => true irb(main):002:0> require 'mysql' => true irb(main):003:0> require 'postgres' => true
Install web servers:
apt-get install apache2 libapache2-mod-php5 php5-mysqli php5-pgsql php5-gd apt-get install lighttpd
Phew - that's it for package installation.
Part 2: Configuration
First set up your databases - I won't cover how to do that here. Next set up your Apache2 sites - by default Apache2 will greedily bind to all available interfaces, so you will need to edit the configuration to restrict it to only one IP, leaving the other free for Lighty. This is done by editing the /etc/apache2/ports.conf file to look something like this:
Listen 192.168.10.3:80
Then create your Apache2 virtual host configurations. In Ubuntu, vhosts live in /etc/apache2/sites-available and are activated using the command "a2ensite". In this example, I'm creating a site for example.com by creating the file /etc/apache2/sites-available/example.com as follows:
<VirtualHost 192.168.10.3:80> ServerName www.example.com ServerAlias example.com DocumentRoot /home/web/example.com/public_html Alias /webstat /home/web/example.com/stats CustomLog /home/web/example.com/logs/access.log combined ErrorLog /home/web/example.com/logs/error.log </VirtualHost>
Note that the VirtualHost declaration uses the IP explicitly. In order for this to work, you must also edit the file /etc/apache2/sites-available/default which contains the NameVirtualHost directive, so that the first 2 lines read as follows:
NameVirtualHost 192.168.10.3:80 <VirtualHost 192.168.10.3:80>
Then activate your site and restart Apache2:
a2ensite example.com /etc/init.d/apache2 restart
Netstat should now show Apache2 listening only on the desired IP:
root@ubuntu:/# netstat -ltnp | grep apache2 tcp 0 0 192.168.10.3:80 0.0.0.0:* LISTEN 7187/apache2
Now its time to set up your Rails sites using Lighty. Here is a sample /etc/lighttpd/lighttpd.conf file for a couple of Rails sites, rails1.example.com and rails2.example.com. Note the server.bind line to restrict Lighty to its own IP and avoid clashing with Apache2:
server.modules = ("mod_rewrite", "mod_accesslog", "mod_fastcgi", "mod_status")
server.bind = "192.168.10.4"
server.port = 80
server.username = "www-data"
server.groupname = "www-data"
server.pid-file = "/var/run/lighttpd.pid"
accesslog.filename = "/var/log/lighttpd/access.log"
server.errorlog = "/var/log/lighttpd/error.log"
server.indexfiles = ( "index.html" )
server.document-root = "/var/www/"
# assign mime types from /etc/mime.types
include_shell "/usr/share/lighttpd/create-mime.assign.pl"
#### status module
status.status-url = "/server-status"
# first rails site
var.rails1 = "/home/web/rails1.example.com"
$HTTP["host"] == "rails1.example.com" {
server.document-root = var.rails1 + "/public"
url.rewrite = ( "^/$" => "index.html", "^([^.]+)$" => "$1.html" )
server.error-handler-404 = "/dispatch.fcgi"
accesslog.filename = "/home/web/rails1.example.com/log/access.log"
server.errorlog = "/home/web/rails1.example.com/log/error.log"
server.indexfiles = ( "index.html" )
fastcgi.server = ( ".fcgi" =>
( "localhost" =>
( "min-procs" => 1,
"max-procs" => 2,
"socket" => "/tmp/rails1.fcgi.socket",
"bin-path" => var.rails1 + "/public/dispatch.fcgi",
"bin-environment" => ( "RAILS_ENV" => "production" )
)
)
)
}
# second rails site
var.rails2 = "/home/web/rails2.example.com"
$HTTP["host"] == "rails2.example.com" {
server.document-root = var.rails2 + "/public"
url.rewrite = ( "^/$" => "index.html", "^([^.]+)$" => "$1.html" )
server.error-handler-404 = "/dispatch.fcgi"
accesslog.filename = "/home/web/rails2.example.com/log/access.log"
server.errorlog = "/home/web/rails2.example.com/log/error.log"
server.indexfiles = ( "index.html" )
fastcgi.server = ( ".fcgi" =>
( "localhost" =>
( "min-procs" => 1,
"max-procs" => 2,
"socket" => "/tmp/rails2.fcgi.socket",
"bin-path" => var.rails2 + "/public/dispatch.fcgi",
"bin-environment" => ( "RAILS_ENV" => "production" )
)
)
)
}
Having created your config file, fire up Lighty (if you installed Apache2 first, it won't be running already, as port 80 was in use on all interfaces at that point):
/etc/init.d/lighttpd start
Part 3 - Advanced Stuff
So far, so good. By now you should have a server that can host pretty much anything you can throw at it, including multiple Rails sites as well as multiple Apache2/PHP sites, and these sites can use MySQL or PostgreSQL databases.
But what if you have a site that needs both? Supposing you have a PHP-based site running happily on Apache2, and you want to add a typo blog in a subdirectory, say coolsite.example.com/blog. You can't point the domain to both IPs, but what you can do is use Apache2's mod_proxy module to proxy the /blog subdirectory to Lighty. Your Apache2 vhost should look something like this:
<VirtualHost 192.168.10.3:80> ServerName coolsite.example.com DocumentRoot /home/web/coolsite.example.com/public_html Alias /webstat /home/web/coolsite.example.com/stats CustomLog /home/web/coolsite.example.com/logs/access.log combined ErrorLog /home/web/coolsite.example.com/logs/error.log ProxyPass /blog http://coolblog/blog ProxyPassReverse /blog http://coolblog/blog ProxyPreserveHost off </VirtualHost>
You also need to enable mod_proxy:
a2enmod proxy /etc/init.d/apache2 force-reload
This will cause Apache2 to proxy all requests for /blog to http://coolblog/blog. So you need to add an appropriate line to /etc/hosts:
192.168.10.4 coolblog
Then remove the symbolic link /etc/apache2/mods-enabled/proxy.conf, as by default this will deny access:
rm /etc/apache2/mods-enabled/proxy.conf
You can then set up a vhost in Lighty for the blog:
var.coolblog = "/home/web/blog"
$HTTP["host"] == "coolblog" {
server.document-root = var.coolblog + "/public"
url.rewrite = ( "^/$" => "index.html", "^([^.]+)$" => "$1.html" )
server.error-handler-404 = "/dispatch.fcgi"
accesslog.filename = "/dev/null"
fastcgi.server = ( ".fcgi" =>
( "localhost" =>
( "min-procs" => 1,
"max-procs" => 2,
"socket" => "/tmp/coolblog.fcgi.socket",
"bin-path" => var.coolblog + "/public/dispatch.fcgi",
"bin-environment" => ( "RAILS_ENV" => "production" )
)
)
)
}
And finally, add the following line to config/environment.rb in the typo installation:
ActionController::AbstractRequest.relative_url_root = "/blog"
And there you have it. You can now point the domain to the Apache2 IP, and it will proxy the /blog namespace through to Lighty. Note that the Lighty vhost is set to send its access log to /dev/null - this is because all the proxied requests will be logged by Apache2, and in any case, the Lighty logs will only show the requests as coming from Apache2, which doesn't make for very useful logs.
Wrap-up
Feel free to pick and choose bits from this tutorial - everyone's situation is different. But if what I've written here helps you, or you find a bug in my carefully-tested procedure, please leave a comment. In the meantime, happy configuring!
Thanks for Great article! It was really helpful.
Great article, man! Thanks.
E: Couldn't find package irb1.8
Alan, enable your universe repositories in /etc/apt/sources.list
Nice article... this helped a lot.
I'm confused, how do I access rails1.example.com (for example) from the WAN without Apache2 wanting to handle it? Or am I missing the point? Do I have to set up a firewall rule or something to forward traffic to the lighthttpd IP?
Thanks!
"I'm assuming you have more than one public IP available to your server. If you don't, you will need to run Lighty on a different port and proxy all your Rails sites through from Apache, as described in part 3 of this article, and also in more depth in this article...."
Never mind, thanks...
I don't understand why you even need Apache at all. Lighty can serve up PHP via FCGI just fine. Your setup seems unnecessarily complicated.
@Matthew you totaly right! don't understenbd too, why he setup the apache...
Going to play with it, right away. Will post updates. Cool bit.
Great tutorial. A thru Z is covered in it.
I read it from beginning to end and learned a great deal.Thanks for sharing nice stuff.