Introduction

As ungleich focusses on educated customers, we meet pretty cool infrastructures from time to time. In some sense I count local.ch as a customer: they supported me with one day off per week so I was able to found ungleich and acquire first customers. This article is dedicated to local.ch and describes a very elegant solution for Ruby on Rails hosting.

Overview

The setup consists of the following services, glued together in an elegant way:

Nginx

The great trick of the setup is that nginx is used to forward requests to a unix socket that depends on the hostname, which is exposed as $host by nginx. The following configuration snippet contains the important parts:

server {
    listen 80;

    location @error_page {
        root /var/nginx/$host/current/public;
        internal;
        [...]

    location ~ "^/assets/.*-[a-z0-9]{32}.\w+" {
        root /var/nginx/$host/current/public;
        [...]

    location ~ ^/assets/ {
        root /var/nginx/$host/current/public;
        [...]

    root /var/nginx/$host/current/public;
    location @unicorn {
        proxy_pass http://unix:/var/nginx/$host/unicorn.sock;

        # Forward original host name to be seen in unicorn
        proxy_set_header Host $host;

        # Server name and address like being available in PHP
        proxy_set_header SERVER_NAME $server_name;
        proxy_set_header SERVER_ADDR $server_addr;

        # The real client IP address - header has ben setup by Zeus
        proxy_set_header X-Real-IP   $http_x_cluster_client_ip;

        # Needed second header for rails - See SYS-1587
        proxy_set_header X_FORWARDED_FOR $http_x_cluster_client_ip;

As you can see, all paths are dependent on the actual hostname as setup by nginx.

Application Deployment

Applications are deployed under their project name below /var/nginx (like ws-locomotive.dev-deploy or ws-locomotive.master). As you can see from the naming, developers can deploy one application from different branches easily (dev-deploy and master branches is this case). Developers can use Capistrano to deploy their applications and don't need to interact (reload/restart) with nginx, as it is already configured to accept any hostname.

Name Server Configuration

As you can imagine, it would be quite cumbersome for developers to reach a host named ws-locomotive.dev-deploy. That is why a wildcard domain is configured to point to the host running nginx:

*.play.intra.local.ch. CNAME rails-dev-vm-snr01.intra.local.ch.  

Give the application a name

A new hostname can be assigned to an application simply by symlinking it to the application:

% cd /var/nginx
% ln -s ws-locomotive.dev-deploy my-fancy-name.play.intra.local.ch

This way, developers can use any name below play.intra.local.ch for their application. Some applications actually behave differently depending on the name they are accessed with:

info.ws-locomotive.master.play.intra.local.ch -> ws-locomotive.master
hp.ws-locomotive.master.play.intra.local.ch -> ws-locomotive.master

Conclusions

The setup is pretty elegant, because it allows developers to create new development environments without interacting with any sysadmin to configure nginx, bind or whatsoever. There is a security drawback though: An attacker could try to use hostnames like ../../../../etc/ and request the file passwd. That is the reasion why this service is not exposed to the outside world directly, but all external requests are filtered (whitelisting) by a load balancer in front of the rails hosts.