Running Redmine on Tomcat with JRuby
Today I’ll show how to get a relatively complex Rails application (here: the Redmine project management app) up and running in a Java Application server.
Involved are:
- a JDK
- the brand new JRuby 9.0.0.0
- the Tomcat 8 servlet container
- PostgreSQL
- and, of course, Redmine, version 3.1
To a certain degree you will be able to deploy to JBoss or any other container by following this advice, too. You should also be able to adapt most of the following to other Rails apps.
Java
If you are only remotely interested in running Redmine on JRuby you probably already have Java installed and can skip this step. In short: for Mac or Windows, get the latest JDK from Oracle, on Linux, go there as well, or use your package manager to install the latest openJDK.
Check it’s working:
$ java -version
java version "1.7.0_79"
OpenJDK Runtime Environment (IcedTea 2.5.5) (7u79-2.5.5-1~deb7u1)
OpenJDK 64-Bit Server VM (build 24.79-b02, mixed mode)
JRuby
Go to jruby.org and download the latest stable release of the JRuby 9 series.
Unzip it somewhere, and add JRUBY_HOME/bin
to your PATH
, replacing
JRUBY_HOME
with the directory where your JRuby now actually is. Check if it
works:
$ jruby -v
jruby 9.0.0.0.rc2 (2.2.2) 2015-07-09 ff331eb OpenJDK 64-Bit Server VM 24.79-b02 on 1.7.0_79-b14 +jit [linux-amd64]
How to Handle Multiple Ruby Versions
It’s a bit out of scope for this article, but if for whatever reason you are working with more than one version of Ruby, it really pays off to invest some time in a decent Ruby version management and switching setup.
There are many ways to do this, personally I prefer to install Rubies via ruby-install, and switch between Ruby versions with chruby:
$ sudo ruby-install jruby 9.0.0.0
$ chruby jruby-9.0.0.0
$ ruby -v
This way I have all Ruby versions globally available in /opt/rubies
, and chruby
takes care of the path wrangling to make the desired ruby
command (and any related
commands like gem
, bundle
etc) available.
To automate things further I use direnv for switching to the
desired version when I enter any project directory. After installing direnv, simply
create a file named .envrc
in your project directory, which will then be executed
whenever you cd
there:
source /usr/local/share/chruby/chruby.sh
chruby jruby-9.0.0.0
This approach works equally well on OSX and Linux and makes handling different projects with different Ruby version requirements absolutely effortless.
An alternative approach is to use RVM, which puts all of the above (and a lot more) in a single tool.
Redmine
Get Redmine, either by downloading the latest stable release from Redmine.org, or from github:
git clone git@github.com:redmine/redmine.git
cd redmine
git checkout -b 3.1-stable origin/3.1-stable
cp config/database.yml.example config/database.yml
Edit database.yml to suit your needs, also create dev, production and test databases. We will use the development db for running the app on jruby but outside tomcat, and the production db for the deployed app running in tomcat.
gem install bundler
bundle install
bundle exec rake db:migrate
bundle exec rake generate_secret_token
You will see a warning about incomplete support for AR 4.2 by the AR jdbc adapter, but from looking at the linked github issue it appears to be mostly there, especially for the more common rdbms like mysql and postgresql, so there are no problems to be expected.
If you study the Redmine Installation Manual, you will notice that besides the potential JDBC issues, the Loofah library is mentioned as a show stopper since it is required by Rails 4.2, but does not work properly with JRuby. You can read more about this issue in a previous article of mine, the short version is that by using an alternative HTML sanitizer you don’t have to care about Loofah anymore.
To do so, create a file named Gemfile.local
including the external Sanitizer:
platforms :jruby do
gem 'rails-html-sanitizer-jruby', github: 'jkraemer/rails-html-sanitizer-jruby'
end
Don’t forget to run bundle install
to actually install the additional gem
after making that change.
You are now ready to give Redmine on JRuby a first test drive by firing up the development environment:
bundle exec rails s
Running the test suite takes a while, but should complete without failures. Redmine’s test suite is quite large, if you are hit by one of the JDBC adapter issues mentioned above you should notice it now.
bundle exec rake db:migrate RAILS_ENV=test
bundle exec rake
Building a WAR and deploying to Tomcat
No need to be afraid, this is surprisingly easy.
Install Tomcat
Download the latest release from tomcat.apache.org
and unpack the archive at a convenient location. For the rest of the article
I’m assuming tomcat resides in ~/apache-tomcat-8.0.24
.
Fire up tomcat:
$ cd ~/apache-tomcat-8.0.24
$ bin/startup.sh
... some environment variables
Tomcat started.
If you now point your browser to localhost:8080, you should see the default tomcat welcome page.
Build and deploy the WAR file
WAR files are just glorified ZIP archives with a somewhat standardized directory layout. If everything works as intended, they make delivering / deploying a web application a breeze - you drop the file into the application server and are done. As always the devil’s in the details, but for our simple scenario we come very close.
Warbler is a Ruby gem that will turn your Rails (or, more generally speaking, Rack) app into a single, ready-to-deploy WAR file. It looks at your Gemfile, collects all dependencies (including JRuby itself) and adds some meta data as well as JRuby-Rack which adapts the Java Servlet API to Rack.
First, add Warbler to your Gemfile.local. In order to get JRuby 9.0.0.0 support we have to use Warbler 2.0 which as of this writing is available as a pre-release version only:
platforms :jruby do
gem 'rails-html-sanitizer-jruby', github: 'jkraemer/rails-html-sanitizer-jruby'
gem 'warbler', '2.0.0.pre3', group: :development
end
Bundle install should pull down warbler and some dependencies. Then we let Warbler generate its config file:
bundle install
bundle exec warble config
That generates config/warble.rb
, which needs to be adjusted slightly so Redmine’s
Gemfile.local
is included:
# Additional files/directories to include, above those in config.dirs
config.includes = FileList["Gemfile.local"]
Before building the WAR file you should also have a look at Redmines
configuration. There is a sample config file located in
config/configuration.yml.sample
, copy that to config/configuration.yml
and
edit it. At the very least you should configure the various email settings, and
set attachments_storage_path
to a directory where uploaded files should go.
By default this is Rails.root/files
, which would, depending on how the
application server handles WAR files, be either inside the WAR file (usually
this is not writable by the application), or, if the server unpacks WAR files
(which Tomcat does by default), inside $TOMCAT_HOME/webapps/redmine/
, which is
most probably writable, but still not suitable for a production setup.
With that out of the way, it’s time to build a WAR archive:
warble war
cp redmine.war $TOMCAT_HOME/webapps
Now that was easy! Redmine should now run in Tomcat and be reachable at
localhost:8080/redmine
.
Next is to have Redmine use Tomcat’s native database connection pool, but I’ll leave that for another post.
Comments
Yuan
[sysadmin@yftestserver redmine-3.1.1]$ warble war
warble aborted!
TypeError: no implicit conversion from nil to integer
/home/sysadmin/.gem/jruby/2.2.2/gems/warbler-2.0.0.rc1/lib/warbler/traits/bundler.rb:129:in `relative_from_pwd'
/home/sysadmin/.gem/jruby/2.2.2/gems/warbler-2.0.0.rc1/lib/warbler/traits/bundler.rb:76:in `add_bundler_gems'
/home/sysadmin/.gem/jruby/2.2.2/gems/warbler-2.0.0.rc1/lib/warbler/traits/bundler.rb:31:in `after_configure'
/home/sysadmin/.gem/jruby/2.2.2/gems/warbler-2.0.0.rc1/lib/warbler/traits.rb:33:in `block in after_configure'
/home/sysadmin/.gem/jruby/2.2.2/gems/warbler-2.0.0.rc1/lib/warbler/traits.rb:33:in `after_configure'
/home/sysadmin/.gem/jruby/2.2.2/gems/warbler-2.0.0.rc1/lib/warbler/config.rb:213:in `initialize'
/home/sysadmin/.gem/jruby/2.2.2/gems/warbler-2.0.0.rc1/lib/warbler/task.rb:48:in `initialize'
/home/sysadmin/.gem/jruby/2.2.2/gems/warbler-2.0.0.rc1/lib/warbler/application.rb:27:in `load_rakefile'
/home/sysadmin/.gem/jruby/2.2.2/gems/warbler-2.0.0.rc1/lib/warbler/application.rb:77:in `run'
/home/sysadmin/.gem/jruby/2.2.2/gems/warbler-2.0.0.rc1/bin/warble:11:in `'
/home/sysadmin/.gem/jruby/2.2.2/bin/warble:1:in `'
/home/sysadmin/.gem/jruby/2.2.2/bin/jruby_executable_hooks:15:in `'
Jens