Setting up a cheap Redmine server using Unicorn and Apache

Redmine is an excellent web-based project management tool. It’s open source, free and is built using Ruby on Rails.

We use Redmine as our main tool for planning, issue tracking and documenting processes. It’s an everyday tool so we need to have an always ready Redmine instance within our private domain.

Get a small VPS server

We use an Amazon EC2 t1.micro instance running Ubuntu Precise 64-bit as development server.

This machine is mainly managing a Git server with dozens of repos (and backing them up as a S3 filesystem) and a Redmine instance.

As described on Amazon EC2 docs, this instance type has pretty basic resources, but are enough for running some lightweight processes with low I/O load.

Add your service user

We’re so bad choosing funny names so our service username is service.

Redmine

Log in as your service user and clone the latest Redmine tree:

$ mkdir -p ~/apps/redmine
$ git clone git://github.com/redmine/redmine.git ~/apps/redmine

Install and set up your database engine server (MySQL, PostgreSQL…) and edit the ~/apps/redmine/config/database.yml file to match these settings. Then you’ll be ready to set up the production environment:

$ cd ~/apps/redmine
$ bundle install --without development test
$ RAILS_ENV=production bundle exec rake db:migrate

Unicorn

“unicorn” gem

Add the unicorn gem as an application dependency by adding a Gemfile.local file (unobtrusive way to add dependencies) with this content:

# ~/apps/redmine/Gemfile.local

source 'https://rubygems.org'

gem 'unicorn'

After that you should run bundle install again:

$ cd ~/apps/redmine
$ bundle install --without development test

Filesystem

We do store all shared resources such as config, logs or process pids in a folder named shared:

$ mkdir -p ~/shared/{config/redmine,log/redmine,pid/redmine,socket/redmine}

The previous command will create the following structure under /home/service:

├── shared
│   ├── config
│   │   └── redmine
│   ├── log
│   │   └── redmine
│   ├── pid
│   │   └── redmine
│   └── socket
│       └── redmine

Config

$ curl -o ~/shared/config/redmine/unicorn.rb https://raw.github.com/defunkt/unicorn/master/examples/unicorn.conf.rb

Once you get the Unicorn config example, you will need to tweak a few lines:

  • worker_processes: Number of Unicorn workers you need.
  • working directory: working_directory "/home/service/apps/redmine"
  • pid: pid "/home/service/shared/pid/redmine/unicorn.pid"
  • socket: listen "/home/service/shared/socket/redmine/unicorn.sock"
  • log: stderr_path "/home/service/shared/log/redmine/unicorn.stderr.log" stdout_path "/home/service/shared/log/redmine/unicorn.stdout.log"

“Automate everything”

We put all service-related scripts in the ~/script directory. These commands might help you on launching and stopping Unicorn processes:

cd /home/service/apps/redmine && unicorn_rails -c /home/service/shared/config/redmine/unicorn.rb -p 5000 -E production -D

Command explanation:

  • -c sets the config file (/home/service/shared/config/redmine/unicorn.rb)
  • -p sets the port of the master Unicorn process (5000)
  • -E sets the environment (production)
  • -D daemonizes the command
# ~/script/stop-redmine-unicorn

kill -QUIT $(cat /home/service/shared/pid/redmine/unicorn.pid)

Because Unicorn is Unix you can send a QUIT signal to the master process to stop all its workers.

Apache

Modules

You will need to enable some Apache2 modules:

sudo a2enmod rewrite
sudo a2enmod proxy
sudo a2enmod proxy_balancer
sudo a2enmod proxy_http

Proxy Unicorn processes

Now we have to set up a new VirtualHost as a proxy balancer of the master Unicorn process running at 127.0.0.1:5000:

<VirtualHost *:80>
ServerAdmin admin@yourdomain.com
ServerName redmine.yourdomain.com
DocumentRoot /home/service/apps/redmine/public
RewriteEngine On
<Proxy balancer://unicornservers>
BalancerMember http://127.0.0.1:5000
</Proxy>
# Redirect all non-static requests to thin
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
RewriteRule ^/(.*)$ balancer://unicornservers%{REQUEST_URI} [P,QSA,L]
ProxyPass / balancer://unicornservers/
ProxyPassReverse / balancer://unicornservers/
ProxyPreserveHost on
<Proxy *>
Order deny,allow
Allow from all
</Proxy>
# Disable log writing for reducing I/O load
ErrorLog /dev/null
</VirtualHost>

Note that Apache logging was disabled for reducing I/O load. Feel free to set it up if your resources are not being affected by I/O operations.

Applying it all

After enabling the new VirtualHost and restarting the apache2 service, a proxy will be listening for processes on the specified host and port.

Now run the Unicorn process as shown above (launch-redmine-unicorn script) and Redmine application should start and be served through the new VirtualHost.

Now it’s time to do some tests in order to adjust the number of Unicorn workers you need to have running on your server.

Bonus: Setting up Nginx as a frontend of Unicorn

As some of you have mentioned, Nginx is generally a better choice for micro instances. It consumes much less RAM and it’s faster than Apache due its event-driven approach.

Setting up Nginx instead of Apache2 as a frontend of Unicorn processes is trivial too so there we go:

First of all, ensure you have defined the Unicorn socket path on unicorn.conf.rb (listen option), so your socket can be located in a path like this one: /home/service/shared/socket/redmine/unicorn.sock.

Then, we’ll tweak some options from the sample nginx.conf file shipped with Unicorn in order to serve our Unicorn processes:

# this can be any application server, not just Unicorn/Rainbows!
upstream redmine_unicorn {
# fail_timeout=0 means we always retry an upstream even if it failed
# to return a good HTTP response (in case the Unicorn master nukes a
# single worker for timing out).
# for UNIX domain socket setups:
server unix:/home/service/shared/socket/redmine/unicorn.sock fail_timeout=0;
# for TCP setups, point these to your backend servers
# server 192.168.0.7:8080 fail_timeout=0;
# server 192.168.0.8:8080 fail_timeout=0;
# server 192.168.0.9:8080 fail_timeout=0;
}
server {
# enable one of the following if you're on Linux or FreeBSD
# listen 80 default deferred; # for Linux
# listen 80 default accept_filter=httpready; # for FreeBSD
# If you have IPv6, you'll likely want to have two separate listeners.
# One on IPv4 only (the default), and another on IPv6 only instead
# of a single dual-stack listener. A dual-stack listener will make
# for ugly IPv4 addresses in $remote_addr (e.g ":ffff:10.0.0.1"
# instead of just "10.0.0.1") and potentially trigger bugs in
# some software.
# listen [::]:80 ipv6only=on; # deferred or accept_filter recommended
client_max_body_size 4G;
server_name redmine.yourdomain.com;
# ~2 seconds is often enough for most folks to parse HTML/CSS and
# retrieve needed images/icons/frames, connections are cheap in
# nginx so increasing this is generally safe...
keepalive_timeout 5;
# path for static files
root /home/service/apps/redmine/public;
# Prefer to serve static files directly from nginx to avoid unnecessary
# data copies from the application server.
#
# try_files directive appeared in in nginx 0.7.27 and has stabilized
# over time. Older versions of nginx (e.g. 0.6.x) requires
# "if (!-f $request_filename)" which was less efficient:
# http://bogomips.org/unicorn.git/tree/examples/nginx.conf?id=v3.3.1#n127
try_files $uri/index.html $uri.html $uri @app;
location @app {
# an HTTP header important enough to have its own Wikipedia entry:
# http://en.wikipedia.org/wiki/X-Forwarded-For
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# enable this if you forward HTTPS traffic to unicorn,
# this helps Rack set the proper URL scheme for doing redirects:
# proxy_set_header X-Forwarded-Proto $scheme;
# pass the Host: header from the client right along so redirects
# can be set properly within the Rack application
proxy_set_header Host $http_host;
# we don't want nginx trying to do something clever with
# redirects, we set the Host: header above already.
proxy_redirect off;
# set "proxy_buffering off" *only* for Rainbows! when doing
# Comet/long-poll/streaming. It's also safe to set if you're using
# only serving fast clients with Unicorn + nginx, but not slow
# clients. You normally want nginx to buffer responses to slow
# clients, even with Rails 3.1 streaming because otherwise a slow
# client can become a bottleneck of Unicorn.
#
# The Rack application may also set "X-Accel-Buffering (yes|no)"
# in the response headers do disable/enable buffering on a
# per-response basis.
# proxy_buffering off;
proxy_pass http://redmine_unicorn;
}
# Rails error pages
error_page 500 502 503 504 /500.html;
location = /500.html {
root /home/service/apps/redmine/public;
}
}

Tweaks explained:

  • UNIX domain socket: server unix:/home/service/shared/socket/redmine/unicorn.sock fail_timeout=0; (L8)
  • Name and proxy the upstream server: upstream redmine_unicorn (L2) and proxy_pass http://redmine_unicorn; (L78)
  • Site settings: server_name (L30) and statics root path (38)

Thank you for all your comments.

← Articles archive