4

I have one rails application needed to be deployed by passenger module nginx. This application needs to be served for hundred domain names. I don't have enough memory to launch hundred rails instances. I'm not sure the proper way to launch rails in few instances. It's the same application under different domain names.

server {
    listen 80;
    server_name www.a_domain.com;
    root /webapps/mycook/public;
    passenger_enabled on;
}
server {
    listen 80;
    server_name www.b_domain.com;
    root /webapps/mycook/public;
    passenger_enabled on;
} 
server {
    listen 80;
    server_name www.c_domain.com;
    root /webapps/mycook/public;
    passenger_enabled on;
}

As you can the above code, it would launch three rails instances. It would be nice to launch only instance to serve under these 3 domain. Anyone has some suggestions?

2 Answers 2

9

Just set up multiple domain aliases for that server entry.

server {
    listen 80;
    server_name www.a_domain.com www.b_domain.com www.c_domain.com;
    root /webapps/mycook/public;
    passenger_enabled on;
}

That'll serve requests to each of those domains, and all hit the same app pool.

Sign up to request clarification or add additional context in comments.

3 Comments

+1 Setup multiple aliases and have your Rails app detecting which one should be executed inspecting the host variable.
We've done exactly what Simone suggests in exactly the way Chris describes, and it works fine.
Note: while you can list explicit domain names like this, Nginx also allows wildcards and regex in the server_name. nginx.org/en/docs/http/server_names.html
1

I mentioned in the comments of Chris' answer that you can use wildcards and regex in the server_name and those will get passed to your your rails instance.

server_name *;  # handle requests from all domains

There are four ways to use this in your Rails app (that I'm aware of).

The "Big Bag of Content" Method

Do Nothing

If you just let Nginx send all the domains to the same rails app, they all get the same content. In this scenario, www.abc.com and www.xyz.com can access the same data. It's the easiest to do, because you don't do anything.

The limitation of this solution comes when you want to let the domains have different content. For instance, it gets tricky if www.abc.com/about and www.xyz.com/about are supposed to be different pages.

The "Man Behind the Curtain" Method

Using Nginx's "rewrite"

Under certain circumstances, you can let Nginx rewrite the domain name into a subdomain and pass that to your rails app. For example:

server_name *;  # handle requests from all domains
rewrite ^(?:www.)?([^.]*)\..*$ $1.yourdomain.com last;

That regex needs a little explanation. It just says get the just the xyz from any of these: www.xyz.com, xyz.com, xzy.co.uk, www.xyz.co.uk. The rewrite changes the request from any of those to xyz.yourdomain.com.

The advantage of this is that Nginx does this very quickly, the rails app isn't involved until later, and requesting content can be restricted to the subdomain. Page.where(subdomain: request.subdomain, permalink: params[:permalink]) for example.

This is pretty restrictive, though, because it means the subdomain and domain name have to be the same. Maybe that's a problem for your application, maybe not. But although I use $1 here in the subdomain, you could just as easily insert it as a parameter in the url. For instance, yourdomain.com/$1 would rewrite the request from www.xyz.com to yourdomain.com/xyz.

Another problem with this is "the man behind the curtain". Although the user visits www.xyz.com the rewrite means they'll see xyz.yourdomain.com in the address bar.

The "Rails Way" Method

Using the Rails request object in the ApplicationController

You can use your application controller to scope the content associated with the domain name using Rails' request object.

In this example, we'll use the ApplicationController to look up the user account associated with the domain name. Assuming you have a User model with a domain_name attribute:

def domain_user
  @domain_user ||= User.where(domain_name: request.domain()).take
end

Redis can be used to cache these lookups if the additional hits to your database creates a performance barrier. I haven't reached that point in my apps thus far.

The weakness of this solution is that although the lookup might find www.xyz.com, it would miss xyz.com. To accommodate this, we can use some regex:

def domain_user
  request.domain.match /(?:www.)?(.*)/
  @domain_user ||= User.where(domain_name: $1).take
end

This regex strips off the www. if it is present. The rest becomes the domain (which ruby stores for us in $1).

Unlike the Nginx rewrite solution, if the user visits www.xyz.com, that's what they still see in the address bar.

Using Routing Constraints

The "Doesn't Work for Me" Method

Alternatively, Rails 3 and above have constraints, functions that exist in your routes file that generate the routing table semi-dynamically. I say "semi-dynamically" because the routing table is generated when the app starts. If the User model (in our example) changes, special attention needs to be given to rebuilding the route table. For apps spread across multiple servers, this can become unwieldy to manage, although people have done it.

Thus far, all of my apps have eventually, if not initially, used the ApplicationController solution because it turned out to be the cleanest and most easily implemented. It also makes a lot of sense from the MCV perspective.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.