Why would you want to deploy Rails on Windows? With tools like Capistrano, surely deploying on Linux is the obvious choice. Well there are (at least) two valid reasons to deploy Rails on Windows:
- You don't have a choice. Many companies are Windows shops and maintaining a Linux/OS X/other server is simply not an option. This situation is becoming less common as Linux gains popularity in the server room. However, depending on the scale of your Rails app, you may have to share a server with other services, and this reason still holds.
- You use Microsoft's SQL Server as your Rails database. This is probably not common for new Rails apps in completely new systems (where you have your choice of SQL servers), although SQL Server is still a good product and sensible choice. Rails connects to SQL Server through ADO (at least, this is the method suggested by AWDwR), which is only available on Windows.
I know there are many guides on running Rails, but I didn't find one that covered this particular configuration in detail: LightTPD <- SCGI -> Rails <- ADO -> SQL Server on Windows, running as services. This guide covers vanilla Windows XP/Windows Server 2003 to working web server. It assumes you have a Rails app working in a development environment (such as RadRails), connecting to an existing SQL Server, and committed to an existing SVN server.
Gather and Install Necessary Software
- Ruby (v 1.8.6) - Download the Ruby One-click Installer for Windows. Install to
C:\Ruby\
and include Gems. - LightTPD (v 1.4.18-1) - Download the pre-compiled LightTPD web server (.exe), part of the WLMP project. Install to
C:\LightTPD\
. - Rails (v 2.0.2) - Install Rails via RubyGems. Open the command line and type:
gem update --system
gem install rails
- Zed Shaw's SCGI Rails Runner (v 0.4.3) - Download the gem from Shaw's blog (click the link labeled "gem"). Install .gem file using RubyGems; again, open the command line and type:
gem install C:\path\to\scgi_rails-0.x.x.gem
Replace the path in the above command with the location/file you downloaded the .gem to.
- Subversion (v 1.4.5) - Download the SVN Windows Installer. Install to the default location (
C:\Program Files\Subversion
); this will install the SVN command-line tools.
- Windows Server 2003 Resource Kit Tools - Download rktools.exe. Install to the default location (
C:\Program Files\Windows Resource Kits\Tools
).
- Ruby/DBI ADO (v 0.1.1) - Download the ruby-dbi package from RubyForge. Extract archive somewhere using your favorite tool (such as 7-zip). Install the package from the command line:
cd C:\path\to\ruby-dbi\
ruby setup.rb config --with=dbd_ado
ruby setup.rb setup
ruby setup.rb install
Replace the path in the above command with the location where you extracted ruby-dbi.
- Rails SQL Server adapter gem. This step is optional if you freeze this gem into your application as described below.
I had to fix the SCGI Rails gem to work with Rails 2.0; you may not for later versions of the SCGI Rails Runner. Open the file C:\ruby\lib\ruby\gems\1.8\gems\scgi_rails-0.4.3\bin\scgi_service
using your favorite Ruby editor (or just WordPad), and comment out line 36,
ActiveRecord::Base.threaded_connections = false
so that it looks like this:
#ActiveRecord::Base.threaded_connections = false
Apparently this method is no longer available in Rails 2.0. I have not run into any issues removing this line, but if anyone has any suggestions for fixing it for Rails 2.0, let me know.
Note: Since we're using the command line a lot in this guide, it makes sense to leave the window open. At this point however, you should close all command line windows, because some of the installers add locations to your PATH variable, which only affects new command line windows.
Prepare Rails Application
This guide suggests a configuration to host multiple Rails applications from a single Lighty server. We create a main folder to contain all Rails apps; inside, we put a log folder for the Lighty logs, and a folder for each Rails application. In my particular setup, I use this as a way to run the same intranet Rails app in production and development modes for testing purposes before rolling out changes.
Open a command line and type:
cd C:\
mkdir webapps
cd webapps
mkdir log
svn export svn://your-svn-server/railsapp1/trunk railsapp1
cd railsapp1
scgi_ctrl config -S
Replace "your-svn-server" with the name of your svn server, and "railsapp1" with the name/svn path of your particular Rails application. Note: the last command will ask you for a password. I have no clue what this password is for, so I don't think it's too important. If you know what the password is for let me know.
The scgi_ctrl
command creates a new file in your rails app at C:\webapps\railsapp1\config\scgi.yaml
. For additional apps, you will need to alter this file. For example, to run the app in development mode, you would change the file to look like this:
---
:disable_signals: true
:env: development
:control_url: druby://127.0.0.1:8998
:config: config/scgi.yaml
:host: 127.0.0.1
:port: 9998
:password: LeaveThePasswordAlone
:logfile: log/scgi.log
The useful options are highlighted above. You must specify a different port for each Rails app to run on (LightTPD redirects requests to these ports). You may also change the env setting to a different environment, based on your needs.
Configure Lighty
Copy the folder C:\LightTPD\conf
to C:\LightTPD\conf-backu
p so that you have the original configuration files for future reference. Open C:\LightTPD\conf\lighttpd-srv.conf
in a text editor (again, Wordpad works). Edit the file to match the following:
# LightTPD Configuration file (RUN AS A SERVICE)
#
# Use it as a base for LightTPD 1.0.0 and above.
# This version is built for WLMP Project - http://wlmp.dtech.hu/
#
# $Id: lighttpd-srv.conf,v 1.0 2006/11/03 23:35:28 weigon Exp $
## where to send error-messages to
server.errorlog = "C:/webapps/log/lighttpd-srv.error.log"
#### accesslog module
accesslog.filename = "C:/webapps/log/lighttpd-srv.access.log"
## to help the rc.scripts
#server.pid-file = "C:/LightTPD/logs/lighttpd-srv.pid"
#### include other configfiles
include "C:/LightTPD/conf/lighttpd-tag.conf"
include "C:/LightTPD/conf/lighttpd-inc.conf"
The important changes are highlighted above. Providing absolute paths becomes necessary when running Lighty as a service.
Next, open C:\LightTPD\conf\lighttpd-srv.conf
in your text editor. Replace it with the following, and edit to suit your specific paths:
# LightTPD Configuration file (INCLUDE)
#
# Use it as a base for LightTPD 1.0.0 and above.
#
# This version is a stripped down version for use with SCGI, Rails, and
# multiple hosts on ports.
#
# $Id: lighttpd-inc.conf,v 1.7 2004/11/03 22:26:05 weigon Exp $
############ Options you really have to take care of ####################
## Always has to be a default root
var.home = "C:/webapps"
server.document-root = var.home + "/railsapp1/public"
## modules to load
server.modules = (
"mod_access",
"mod_accesslog",
"mod_alias",
"mod_redirect",
"mod_rewrite",
"mod_scgi",
"mod_ssi",
"mod_status",
)
## App1 Configuration
var.app1 = var.home + "/railsapp1"
$SERVER["socket"] == ":80" {
server.port = 80
server.document-root = var.app1 + "/public"
server.upload-dirs = ( var.app1 + "/tmp" )
server.errorlog = var.home + "/log/app1-errors.log"
accesslog.filename = var.home + "/log/app1-access.log"
static-file.exclude-extensions = ( ".cgi", ".fcgi", ".scgi" )
server.error-handler-404 = "/dispatch.scgi"
scgi.server = ( "dispatch.scgi" => ((
"host" => "127.0.0.1",
"port" => 9999,
"check-local" => "disable"
)) )
}
## App2 Configuration
var.app2 = var.home + "/railsapp2"
$SERVER["socket"] == ":3000" {
server.port = 3000
server.document-root = var.app2 + "/public"
server.upload-dirs = ( var.app2 + "/tmp" )
server.errorlog = var.home + "/log/app2-errors.log"
accesslog.filename = var.home + "/log/app2-access.log"
static-file.exclude-extensions = ( ".cgi", ".fcgi", ".scgi" )
server.error-handler-404 = "/dispatch.scgi"
scgi.server = ( "dispatch.scgi" => ((
"host" => "127.0.0.1",
"port" => 9998,
"check-local" => "disable"
)) )
}
## Applies to all Apps
## files to check for if .../ is requested
# index-file.names = ( "index.html", "index.htm", "default.htm" )
# 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" => "application/ogg",
".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",
".cpp" => "text/plain",
".log" => "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"
)
# Use the "Content-Type" extended attribute to obtain mime type if possible
mimetype.use-xattr = "enable"
######### Options that are good to be but not neccesary to be changed #######
## enable debugging
#debug.log-request-header = "enable"
#debug.log-response-header = "enable"
#debug.log-request-handling = "enable"
#debug.log-file-not-found = "enable"
#### SCGI module
scgi.debug = 0
#### status module
status.status-url = "/server-status"
status.config-url = "/server-config"
#### url handling modules (rewrite, redirect, access)
#url.rewrite = ( "^/$" => "/server-status" )
#url.redirect = ( "^/wishlist/(.+)" => "http://www.123.org/$1" )
#### rewrite to pick up page cache
url.rewrite = ( "^([^.]+)$" => "$1.html" )
Again, portions that you will most likely need to change are highlighted above. Be sure to provide absolute paths.
Finally, with Lighty configured, we want to run that Rails app! Copy the following into a text editor and save it to C:\LightTPD\Start_LightTPD.bat
:
@echo off
c:
cd C:\lighttpd\
echo Starting lighty...
START /B lighttpd.exe -f conf\lighttpd-srv.conf -m lib -D
PAUSE >NUL && EXIT
And save the following to C:\LightTPD\Start_RailsApp1.bat
:
@echo off
c:
cd C:\webapps\railsapp1
echo Starting RailsApp1 SCGI_service...
START /B scgi_service
PAUSE >NUL && EXIT
As always, adjust to suit your particular Rails app. You will want to create a runner batch file for each Rails app you wish to run. (unless you run as services as described below)
Now just run each batch file. A command window should appear for each, print the "Starting x..." line, and stay open. Pop open a web browser, and type http://localhost/
in the address bar. If your Rails app comes up, congratulations! If not, you've got some troubleshooting to do. Check the command windows for any errors; you can also look at the logs in C:\webapps\log
and C:\webapps\railsapp1\log
for errors and additional info.
Setup LightTPD and Rails as Services
Once you have Lighty and Rails running from batch files, you may want to run them as services. Services have two major advantages: they keep running when you logout, and they can run with restricted security privileges.
We are going to use the srvany
program that comes with the Windows Resource Kit to run both Lighty and Rails as services. srvany
acts as a wrapper that allows regular applications to run as a Windows service. Open a command line and type the following:
instsrv lighttpd "C:\Program Files\Windows Resource Kits\Tools\srvany.exe"
instsrv railsapp1 "C:\Program Files\Windows Resource Kits\Tools\srvany.exe"
Create a single service for LightTPD, and one service for every Rails app you wish to run. Just change the name of the service (highlighted above).
This creates two new services, named "lighttpd" and "railsapp1" that both run srvany
. Now we edit the registry to configure what srvany
runs. You can do this manually (using the values listed below), or use a text editor to save the following to lighttpd.reg
(doesn't matter where you put it):
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lighttpd\Parameters]
"Application"="C:\\LightTPD\\LightTPD.exe"
"AppParameters"="-f \"C:\\LightTPD\\conf\\lighttpd-srv.conf\" -m \"C:\\LightTPD\\lib\" -D"
Save the following to railsapp1.reg
(or name of your Rails app):
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\railsapp1\Parameters]
"Application"="C:\\ruby\\bin\\ruby.exe"
"AppParameters"="C:\\ruby\\bin\\scgi_service"
"AppDirectory"="C:\\webapps\\railsapp1"
Save a copy of the .reg file above for each Rails service, changing the registry path and AppDirectory value to point to your service name and Rails app location.
Finally, double-click each of these .reg files to add their contents to the registry.
By default, new services run under (or "log on as") the Local System account. This should be changed to a low-privilege account, in case the Rails environment or LightTPD is compromised. We want to run all of the services in this Guide under the Network Service account.
Open the Services dialog at Start > Control Panel > Admistrative Tools > Services. Find the service "lighttpd" that we created, and double-click it. Click the "Log On" tab at the top of the properties dialog, and click "This account:" radio. Type "NT AUTHORITY\NetworkService" into the account field, and clear the password and confirm fields. Click "OK" in the properties dialog. Repeat these steps for each Rails service you have created.
One last step is needed to get the services to run. Both LightTPD and the SCGI Rails Runner will crash if they don't have write permissions to their respective log directories. (As a bonus, when running as services under srvany
, they crash silently, not even appearing to have stopped, and having no way to report the error) Using Explorer, navigate to C:\webapps\,
right-click the log
folder, and select "Properties". Click the "Security" tab, and click "Add...". Type "Network Service" into the object names field, and click "OK". Back in the Properties dialog, make sure "NETWORK SERVICE" is selected in the upper user name list, and check the box in the lower permissions list to allow writes (row "Write", column "Allow"). Click "OK" on the properties dialog. Now navigate to C:\webapps\railsapp1\
, etc. and repeat for the log
folder inside each Rails app.
Now to test the services. Make sure to close any open command windows that are running LightTPD and/or your Rails apps (such as the batch files created above). Start the services using the Services dialog (Start > Control Panel > Admistrative Tools > Services) or from the command line:
NET START lighttpd
NET START railsapp1
At this point, I'd recommend starting Lighty and just one Rails app, then checking if it is running by pointing a browser at http://localhost/
. If it works, start up and test the rest of your Rails apps and call it a day! If you get a blank page, you will have to troubleshoot your configuration. The only good way to do that is to run cmd
using srvany
. One gotcha: Microsoft's solution uses Local System with the "interact with desktop" feature; however, this only works when you are logged directly into the machine, not over a Remote Desktop connection. Once you have cmd
running as a service, try running your batch files from before and troubleshoot any errors reported.
Automating deployment
A couple tips to automate deployment: