mongrel with launchd and lighttpd

I just switched my web apps over to mongrel, the new Ruby web server that’s gotten so much talk lately. It’s striking to see how much easier web application development has gotten in the last year. I have a few light web applications, and they are all now running with mongrel. I’m running a separate mongrel server for each and using lighttpd to proxy to them and serve a few static sites. Everything is running on a mac mini and is configured to be run automatically by launchd, the daemon manager on OS X.

Just like the mongrel docs say, you can install it with
$ sudo gem install mongrel
I was prompted to install a couple of dependencies that I didn’t have, so the next time I do this, I’ll add the —include-dependencies option to gem install. The next step is to go inside one of your rails application directories and start mongrel.
$ mongrel_rails start
The -d option will make mongrel go into the background as a daemon. Leave it off and the process will stay running at the console, and you can break out of it with control-C. When it starts it will tell you the port that it’s using. The default is 3000, so look for your web application at http://localhost:3000.

After verifying each of my applications with mongrel, I decided to switch. I had previously been using lighttpd to launch fcgi processes, generally following these instructions from James Duncan Davidson. I wondered briefly how I might run multiple rails apps under mongrel, then realized that it was probably best (and maybe only possible) to run a separate mongrel server for each app. So I’m keeping my lighttpd to use primarily as a proxy server.

Now, lighttpd configuration is super-simple. Here’s an erb template of my config file:
server.modules = ("mod_rewrite", "mod_access", "mod_redirect", "mod_fastcgi", "mod_proxy", "mod_compress", "mod_accesslog")
server.document-root    = "/Sites/blank/public/" 
server.errorlog         = "/var/log/lighttpd/error_log" 
server.dir-listing      = "disable" 

## files to check for if .../ is requested
server.indexfiles       = ("index.html", "index.htm", "index.rb")

# mimetype mapping
mimetype.assign             = (
  ".pdf"          =>      "application/pdf",
  ".sig"          =>      "application/pgp-signature",
  ".spl"          =>      "application/futuresplash",
  ".class"        =>      "application/octet-stream",
  ".ps"           =>      "application/postscript",
  ".torrent"      =>      "application/x-bittorrent",
  ".dvi"          =>      "application/x-dvi",
  ".gz"           =>      "application/x-gzip",
  ".pac"          =>      "application/x-ns-proxy-autoconfig",
  ".swf"          =>      "application/x-shockwave-flash",
  ".tar.gz"       =>      "application/x-tgz",
  ".tgz"          =>      "application/x-tgz",
  ".tar"          =>      "application/x-tar",
  ".zip"          =>      "application/zip",
  ".mp3"          =>      "audio/mpeg",
  ".m3u"          =>      "audio/x-mpegurl",
  ".wma"          =>      "audio/x-ms-wma",
  ".wax"          =>      "audio/x-ms-wax",
  ".ogg"          =>      "audio/x-wav",
  ".wav"          =>      "audio/x-wav",
  ".gif"          =>      "image/gif",
  ".jpg"          =>      "image/jpeg",
  ".jpeg"         =>      "image/jpeg",
  ".png"          =>      "image/png",
  ".xbm"          =>      "image/x-xbitmap",
  ".xpm"          =>      "image/x-xpixmap",
  ".xwd"          =>      "image/x-xwindowdump",
  ".css"          =>      "text/css",
  ".html"         =>      "text/html",
  ".htm"          =>      "text/html",
  ".js"           =>      "text/javascript",
  ".asc"          =>      "text/plain",
  ".c"            =>      "text/plain",
  ".conf"         =>      "text/plain",
  ".text"         =>      "text/plain",
  ".txt"          =>      "text/plain",
  ".dtd"          =>      "text/xml",
  ".xml"          =>      "text/xml",
  ".mpeg"         =>      "video/mpeg",
  ".mpg"          =>      "video/mpeg",
  ".mov"          =>      "video/quicktime",
  ".qt"           =>      "video/quicktime",
  ".avi"          =>      "video/x-msvideo",
  ".asf"          =>      "video/x-ms-asf",
  ".asx"          =>      "video/x-ms-asf",
  ".wmv"          =>      "video/x-ms-wmv",
  ".bz2"          =>      "application/x-bzip",
  ".tbz"          =>      "application/x-bzip-compressed-tar",
  ".tar.bz2"      =>      "application/x-bzip-compressed-tar" 
)

#Server ID Header
server.tag                 = "neontology" 

#### accesslog module
accesslog.filename          = "/var/log/lighttpd/access_log" 

## deny access the file-extensions
# ~    is for backupfiles from vi, emacs, joe, ...
# .inc is often used for code includes which should in general not be part of the document-root
url.access-deny             = ( "~", ".inc" )

######### Options that are good to be but not neccesary to be changed #######

## bind to port (default: 80)
server.port                =  80

## to help the rc.scripts
server.pid-file            = "/var/run/lighttpd.pid" 

#
# proxies
#
<%
[[APPNAME1, DOMAIN_REGEX1, PORT1],
 [APPNAME2, DOMAIN_REGEX2, PORT2],
 ...
].each do |app| %> 
$HTTP["host"] =~ "<%= app[1] %>" {proxy.server = ("" => (("host" => "127.0.0.1", "port" => <%= app[2] %>)))}
<% end %>

#
# static sites
#
<%
[[SITENAME1, DOMAIN_REGEX1],
 [SITENAME2, DOMAIN_REGEX2],
 ...
].each do |site|
%>
$HTTP["host"] =~ "<%= site[1] %>" {
  server.document-root = "/Sites/<%= site[0] %>/public" 
  server.errorlog = "/Sites/<%= site[0] %>/log/error_log" 
  accesslog.filename = "/Sites/<%= site[0] %>/log/access_log" 
}
<% end %>
By convention, I have a separate directory for each of my rails apps under /Services and a directory for each static site under /Sites. The DOMAIN_REGEX values are regular expressions that match the domains for each application or site. The PORT values are the ones I’ve assigned to each mongrel server. To generate the lighttpd.conf file, I run:
erb lighttpd.erb > /etc/lighttpd/lighttpd.conf
I’ve configured everything to run automatically under launchd, the daemon manager for OS X. Each process needs a configuration file that I’ve put in /Library/LaunchDaemons. Here’s the one that I use for lighttpd:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" 
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>lighttpd</string>
    <key>OnDemand</key>
    <false/>
    <key>Program</key>
    <string>/usr/local/sbin/lighttpd</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/sbin/lighttpd</string>
        <string>-f/etc/lighttpd/lighttpd.conf</string>
        <string>-D</string>
    </array>
</dict>
</plist>
Each mongrel process needs a separate .plist file. I generate all of them with this Ruby script:
#
# generate property lists for all mongrel services to be run on this server
#
services = {
        :SITENAME1 => PORT1,
        :SITENAME2 => PORT2,
        ...
}

template = <<END
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" 
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>SERVICE</string>
    <key>OnDemand</key>
    <false/>
    <key>Program</key>
    <string>/usr/local/bin/mongrel_rails</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/bin/mongrel_rails</string>
        <string>start</string>
        <string>--chdir</string>
        <string>/Services/SERVICE</string>
        <string>--environment</string>
        <string>production</string>
        <string>--port</string>
        <string>PORT</string>
    </array>
</dict>
</plist>
END

services.keys.each {|service| 
        puts "generating property list for mongrel-#{service}" 
        File.open("mongrel-#{service}.plist", "w") {|f|
                port = services[service]
                f.write template.gsub("SERVICE", service.to_s).gsub("PORT", port.to_s)
        }
}
With that, they’ll all be started automatically when my mini reboots. I can load them dynamically using launchctl:
% sudo launchctl load lighttpd.plist
% sudo launchctl load mongrel-typo.plist
...

For more on launchd configuration, see the launchctl man page.

Now we all have more time for other things. Thanks, Zed!

One comment ↓

#1Cyril Godefroy on 2008-06-19 at 02:52:35 America/Los_Angeles

Hi,

I am too trying to migrate away from fcgi with lighttpd to lighttpd + Mongrel. Your setup looks gorgeous, but do you have also good recomendations regarding domain_regex?

Thanks, Cyril

Leave a Comment (sign in with Twitter)