… or any Rack app, really
… or any Rack app, really
There are quite a few options available to deploy Ruby on Rails applications on various platforms. Be it a given datacenter supports nothing else or because we depend on one of its auth modules, Apache often is the first choice. Read on for the painless way to get this done.
Of course, we are happy to do the work for you and host your application on any infrastructure. Don't hesitate to contact us for a quick chat about your project.
All Ruby on Rails applications (framework version 3 and higher) are also Rack applications. Rack applications adhere to a specific interface when connecting to the web server and this allows the web server to be swapped as long as it can handle Rack applications. A few examples are Puma, WEBrick or Unicorn. Thus, all steps below apply to all Rack applications alike.
How do we recognize a Rack app? Its pretty easy: If there is a file called config.ru in the application’s root directory, its almost certainly a Rack app. A convention is to serve css, js, images, fonts etc. from a public directory. Also, most Ruby (and hence Rack) applications nowadays come with a Gemfile.
For this guide, we’ll work with the (very simple and not necessarily useful) clock app. Its directory structure looks essentially like this:
├── config.ru
└── public
├── index.html
└── style.css
It doesn’t do much: The Rack app itself spits out the current time in a JSON response and the static index.html contains some JavaScript to periodically fetch the current time from the Rack app and insert it into the page.
Make sure the clock app is checked out at /var/apps/clock and ensure a suitable user and group. As root, do:
apt-get install git
useradd -m app
git clone https://github.com/wendig-ou/clock.git /var/apps/clock
chown -R app. /var/apps/clock
There are several ways to get this app running:
Of course, there are many more things you could do: For example, you might want to include HAProxy or a caching server in the mix. For now, we will focus on the options mentioned above, to get you started and to cover the most common scenarios.
This is very simple and just requires that Ruby and the gem rack are installed. This is an excellent option for development and quick tests. As root, do:
apt-get install ruby ruby-rack
Now run the app as the app user (how?):
cd /var/apps/clock
rackup
# use e.g. 'rackup -o 0.0.0.0 -p 8080' to change host and port
This will make the app available at http://localhost:9292. Use ctrl-c to stop the process.
This is also very straight forward. You need to install the respective gem (as root):
apt-get install ruby ruby-rack puma
… and then start the process with the app user, e.g.:
cd /var/apps/clock
puma
# use e.g. 'puma -b 0.0.0.0 -p 8080' to change host and port
This will make the app available at http://localhost:9292. Use ctrl-c to stop the process.
You might want to run this on a server. See the section about systemd to learn how to manage this process there.
When deploying your app with Puma, Rack, Thin or WEBrick, you will probably want to make this service run in the background and restart on boot or after crashes. Comes in systemd:
For configuration, we’ll create a file /etc/systemd/system/clock.service as root with the following content:
[Unit]
Description=a clock app
[Service]
User=app
Group=app
WorkingDirectory=/var/apps/clock
ExecStart=/usr/bin/rackup -o 0.0.0.0 -p 9292
Restart=on-failure
[Install]
WantedBy=multi-user.target
Then, we need to reload systemd, enable the unit (makes it start/restart at boot) and start it (starts it now). As root, do:
systemctl daemon-reload
systemctl enable clock.service
systemctl start clock.service
This will also make the app available at http://localhost:9292, but now systemd will take care that the app is restarted when it dies or started after a reboot.
Until now, we used a Ruby process to respond to all incoming web request. That works and might be enough in many situations. It is usually more efficient, to deliver static files with a web server that is optimized for that task and only forward dynamic requests to your app. Also, some apps might simply not handle static files. In this case, you would probably just see an unstyled page without any JavaScript, fonts, icons etc.
We will configure Apache to reverse proxy requests to the Rack application which we started on port 9292 earlier, but only the requests that can’t be served with files from within the public directory. But first, let’s install Apache including some modules as root:
apt-get install apache2
a2enmod rewrite proxy proxy_http
systemctl restart apache2
Then, we create a config file in /etc/apache2/sites-available/001-clock.conf for this page with the following content:
<VirtualHost *:80>
ServerName http://localhost
DocumentRoot /var/apps/clock/public
# allow the location to be accessed at all, this needs to be done because our
# DocumentRoot isn't in /var/www
<Location />
Require all granted
</Location>
# serve requests with files from the public subdirectory if the requested url
# refers to an existing file
RewriteEngine on
RewriteRule ^/?$ /index.html
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
RewriteRule ^/(.*)$ http://127.0.0.1:9292%{REQUEST_URI} [P,QSA,L]
</VirtualHost>
Now, we need to activate this configuration as root (there is probably a default site on your server that you can deactivate):
a2ensite 001-clock
a2dissite 000-default
systemctl restart apache2
And the app should be available at http://localhost.
The guys at Phusion have developed an Apache module which takes care of managing the processes and the reverse proxying. So here, you don’t need to set up a systemd unit. To use it, install it as root:
apt-get install libapache2-mod-passenger
Now, like before, configure Apache with a config file in /etc/apache2/sites-available/001-clock.conf, but this time with the following content:
<VirtualHost *:80>
ServerName http://localhost
DocumentRoot /var/apps/clock/public
<Location />
Require all granted
</Location>
</VirtualHost>
Then activate it as before:
a2ensite 001-clock
a2dissite 000-default
systemctl restart apache2
And the app should be available at http://localhost
Many OS package managers provide Ruby which includes RubyGems. If you decide to
use this Ruby installation, I would also recommend that you use the OS package
manager to install the gems (as we did above). In some cases, this isn’t
possible because you require specific gem versions. But using gem install
directly can easily create conflicts with the gems installed by your OS package
manager. In such a situation, you can:
bundle install --path /var/apps/clock/bundle
. This requires that your gems
(including puma, webrick etc.) are mentioned in the Gemfile.
Assuming you are logged in to your server as root, you can change the user for
the current session (think impersonate) with su app
. After you are done
executing commands as this user, type exit
or hit ctrl-d
.