Multiple customer instances of an application, under a single application.
What I need is to allow multiple users, to connect to my Apache Web server, by passing different url like :
customer1.myhost.com
company1.myhost.com
company2.myhost.com
etc.
What I want my Apache server to do, is pass all request that are not directed to a certain list of existing hosts
(like trac.myhost.com and https://myhost.com) over to my Rails application, by setting a RequestHeader to identify the requested host, something like :
RequestHeader "INSTANCE_NAME" = customer1 #for customer1.myhost.com
Thanks for your help!
Ps.: The end goal is to offer software slices as a service, but having all those customers managed under 1 application running. Not 1 app per customer.
Using a standard VirtualHost configuration you can do this:
NameVirtualHost *:80
<VirtualHost *:80>
ServerName app.example.com
ServerAlias *.example.com
DocumentRoot /web/app.example.com/public
</VirtualHost>
This will capture all requests that are not already captured by other VirtualHost entries.
When your application receives the request, you will have the request variable set with the host-name provided. This is available to any ActionController:
request.host
From there you can load the appropriate data in some kind of before_filter, as is typically done like:
before_filter :load_client
def load_client
#client = Client.find_by_hostname!(request.host)
rescue ActiveRecord::RecordNotFound
render(:partial => 'client_not_found', :status => :not_found)
end
So long as the client has the hostname populated correctly, this will find them on each page load.
Related
I assume this is not really a difficult issue but I am using so many different solutions and don't really know what it best.
I am using Ruby on Rails and have my apps on Heroku and I want to 301 redirect everything on my naked domain (#) to my www-domain. E.g.
http://domain.com --> http://www.domain.com
http://domain.com/subpage --> http://www.domain.com/subpage
Right now I am handling this with my DNS by first deleting both # and www entries. Then I set a redirect of the entire website to http://www.domain.com (which re-creates the DNS entries for both # and www). Lastly I change my www DNS-entry to CNAME and the name of the heroku-app (http://myapp.herokuapp.com).
This seems to be forwarding http://domain.com/subpage to http://www.domain.com (without the subpage).
What I am looking for now is the proper/recommended way to handle this in a simple/elegant way.
DNS?
Routes?
.htaccess? (if so, how do I alter .htaccess in RubyOnRails)
Thanks in advance!
These questions may be related to your question, maybe have a look at those. It seems (from what I have read), that heroku does not allow access to anything like .htaccess or anything, so it seems like Rack middleware would be the best option.
Where is your domain registered? If with GoDaddy, they offer a service that will handle the 301 redirect for you, but it requires that you sign up for one of their hosting plans. The lowest cost will do (~$5 per month). Not free, but painless and requires no coding, etc.
(notice : did not try anything of this, but it should work)
One might argue that the nice way is to use the DNS or your webservers capabilities to do that. However, it is possible to do it with rails if you need it.
All in all, it has the advantage that you will easily keep any params / path in the process, as you told you wanted. Plus, the logic is inside the app and won't be lost if you need to scale up / change domain name. On the bad side, your full stack will be hit anytime someone uses the bare domain name.
you can try a before_filter in your application controller :
before_filter :redirect_to_www
def redirect_to_www
redirect_to subdomain: 'www' unless request.subdomain == 'www'
end
if you want to avoid a "magic redirection" and make it clear for everyone, on rails 3 you can do this directly in the routes:
constraints ->(request){ request.subdomain != 'www' } do
match '(*all)' => redirect( subdomain: 'www' )
end
you should also add the subdomain to your default url options (application controller):
def default_url_options( option = {} )
{subdomain: 'www'}
end
In the end what I had to do was to point the #-DNS to my normal web host (since # demands an IP and Heroku will only accept CNAME) and the www-DNS to Heroku.
At my normal webhost I put a .htaccess that redirected all traffic there to my www-domain like this:
RewriteEngine On
### re-direct to www
RewriteCond %{HTTP_HOST} ^domain.com
RewriteRule (.*) http://www.domain.com/$1 [R=301,L]
I have a multi-tenant Rails app, hosted with Heroku at http://myapp.herokuapp.com. The tenants/accounts are separated via subdomains (and Postgresql schemas). I have then added my own domain as custom domain to the Heroku app. Since Heroku really advice against using A records because of "uptime implications" (https://devcenter.heroku.com/articles/avoiding-naked-domains-dns-arecords) I first tried to put a CNAME record from my custom domain to myapp.herokuapp.com. That worked fine also with subdomains like http://clientaccount.mydomain.com.
The problem began when my client wanted her own domain pointing to their account so that http://www.clientdomain.com showed http://clientaccount.mydomain.com, the most obvious solution seemed to be to create a CNAME record in my clients DNS to point to http://clientaccount.mydomain.com. That did not work, when visiting the address an error message stated that "There is no app configured at that hostname", also host www.clientdomain.comgave:
www.clientdomain.com is an alias for clientaccount.mydomain.com
clientaccount.mydomain.com is an alias for myapp.herokuapp.com
myapp.herokuapp.com is an alias for ar.herokuapp.com
After some VERY confused support from Heroku, they advised my to use A records instead pointed to their three apex IPs. So changed it but it still didn't work. They then told me to add my clients domain as a custom domain in my Heroku settings which I have done without good result.
So my current configuration is as follows:
Myapp at Heroku has following custom domains:
*.mydomain.com
www.clientdomain.com
mydomain.com DNS
*.mydomain.com has three A records pointing at Heroku apex IPs
clientdomain.com DNS
In the DNS for clientdomain.com, clientdomain.com (without www) is redirected to www.clientdomain.com (Not sure how they do it but it seems to work.)
For www.clientdomain.com there's a CNAME record pointing at clientaccount.mydomain.com
Current state
www.mydomain.com resolves correctly.
clientaccount.mydomain.com resolves correctly.
www.clientdomain.com goes to www.mydomain.com (without subdomain)
So the problem is obvously either in the DNS somehow, does the subdomain fall away somewhere since my application obviously doesn't receive it? Or do I have to change some code in Rails in order to handle this?
The subdomain is handled as route constraint, Subdomain class:
class Subdomain
def self.matches?(request)
request.subdomain.present? && Account.find_by_subdomain(request.subdomain)
end
end
Any input is appreciated!
EDIT
I have done everything that is suggested, do I have to change my application controller as well?
application_controller.rb
def handle_subdomain
if #account = Account.find_by_subdomain(request.subdomain)
PgTools.set_search_path #account.id
#current_account = #account
else
#current_account = nil
PgTools.restore_default_search_path
end
end
Your best bet here is to first setup your main domain as follows:
*.mydomain.com as a CNAME to heroku-appname.herokuapp.com
And add *.mydomain.com as a domain name to your app:
$ heroku domains:add *.mydomain.com
Next You'll want to setup an easy way for your users/clients to add their custom domains to your multi-tenant app. This requires two things, adding the domain name to your app and having them setup DNS. Here's the best approach:
add www.myclientdomain.com to your app:
$ heroku domains:add www.myclientdomain.com
Then setup DNS to point into Heroku. You're best off taking a lesson from Heroku's setup and having your clients CNAME to a domain name that you own. This will help avoid lock-in and gives you much more control over where you're directing traffic. so:
CNAME www.myclientdomain.com to proxy.mydomain.com
The CNAME will follow proxy.mydomain.com to heroku-appname.herokuapp.com and then resolve to the Heroku IPs.
Then you should be all set.
Ideally you'll be taking on new customers with custom domains faster than you can add domain names manually so you'll want to automate that step. Using the heroku api and client you can manage your custom domains, programmatically, fron within your app.
You need to do something like:
def handle_subdomain
if #account = Account.find_by_subdomain(request.subdomain) || Account.find_by_domain(request.domain)
PgTools.set_search_path #account.id
#current_account = #account
else
#current_account = nil
PgTools.restore_default_search_path
end
end
Notice Account.find_by_domain(request.domain)—although you might need request.host for the full host, not just the domain component.
I've looked around quite a bit and did find some information on the topic but couldn't get it to work. I have apache/tomcat installed on my webserver within my company. Previously on the same webserver when I was writing CGI applications I was successfully able to get the username of visitors on my site by access the environment var REMOTE_USER. Now that I'm riding on rails, the variable, request.env['REMOTE_USER'] returns nothing.
Please note that I am not running Mongrel which many of the online discussions assume. Also, i'm not looking to build an authentication system, I'm just looking to read in (using REMOTE_USER) the username of the people who access my site.
I'm aware that I would need to tweak my .htaccess (located in my public folder) file to forward this information from apache into my application. By looking at examples on the web, I did change it to..
RewriteCond %{REMOTE_USER} (.+)
RewriteRule ^.*$ - [E=RU:%1]
RequestHeader add X-Forwarded-User %{RU}e
..but this didn't help. Thanks.
If you add X-Forwarded-User, you have to read request.headers['X-Forwarded-User'] and not request.env['REMOTE_USER'].
Put this in the below file and use http_remote_user instead of the request.env hash.
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def http_remote_user
request.env['HTTP_REMOTE_USER'] || request.headers['X-Forwarded-User']
end
helper_method :http_remote_user
end
I struggled with this, but did finally get it working.
Make sure you remember to add RewriteEngine On into the conf file (for me it was inside the VirtualHost section), I forgot and it silently failed, no errors when restarting apache.
Depending on whether you use a .htaccess or a .conf file, you need to change the the RewriteCond rule. The esoteric details - note 5 means we need to use RewriteCond %{LA-U:REMOTE_USER} (.+) if we are inside a conf file.
To help debugging, you can always start by using RequestHeader add X-Forwarded-User 'FixedValue' to make sure the problems are apache not rails or other intermediates.
I'm creating a multi tenant application like shopify and wanna know how can I create custom domain on server that points to the same application instance?
For example:
app1.mysystem.com == www.mystore.com
app2.mystem.com == www.killerstore.com
I need to do that config on CNAME like Google Apps? If so, how can I do that? Is there some good paper showing how this works ?
PS: app1 AND app2 points to the same application!
Thanks
I have a similar setup and am using nginX. What I did for ease of maintenance was accepted all the connections from nginx and did the filtering in my app.
# application_controller.rb
before_filter :current_client
private
def current_client
# I am using MongoDB with Mongoid, so change the syntax of query accordingly
#current_client ||= Client.where(:host => request.host).first
render('/public/404.html', :status => :not_found, :layout => false) unless #current_client
end
You can have your clients have a domain record with there domain/subdomain pointing to you_ip or your_domain_pointing_to_your_ip.com and capture that in a form and save in database. Then alter the query in current_client like:
#current_client ||= Client.or(:host => request.host).or(:alias => request.host).first
I am currently working through something similar and just did the Nginx configuration. This is the way that I did it.
server {
listen 80;
server_name domain1.com domain2.com domain3.com;
rails_env production;
passenger_enabled on;
root /var/www/your_site_folder/current/public;
}
Also make sure to run passenger_pre_start if you are using passenger.
ex: passenger_pre_start http://your_domain.com;
Add one line for each domain that you add into the above config block.
The key here is under server_name. Normally I would use this for a domain using www.domain.com or without the 'www', domain.com. But in this case you can point all domains that you want to hit your app from here and you have your Nginx setup for multi-tenancy.
I'm successfully running subdomains on my mac OSX as outlined by Robby on Rails. I'd like to follow the example code below but I have no idea where to start. How do you edit a DNS setup? Do I need to have a dedicated virtual host to do this? Right now I have a standard (crappy) Dreamhost shared hosting. If I go with a DVH, is Media Temple a good pick?
Any media recs would be helpful - my google search's have only resulted in angry customer/hosting company tirades.
Thanks!
# 1) Point *.example.com in your DNS setup to your server.
#
# 2) Setup an Apache vhost to catch the star pointer:
#
# <VirtualHost *:80>
# ServerName example.com
# ServerAlias *.example.com
# </VirtualHost>
#
# 3) Set the current account from the subdomain
class ApplicationController < ActionController::Base
before_filter :set_current_account
private
def set_current_account
#current_account = Account.find_by_subdomain!(request.subdomains.first)
end
end
# 4) Tie all top-level requests off the current account
class CustomersController < ApplicationController
def index
#customers = #current_account.customers
end
end
I do something similar with Heroku, which supports Wildcard DNS, definitely worth a look. There are a range of options for handling the DNS - most of the time this is actually handled by whoever is managing your name servers.
Dreamhost will allow Wildcard DNS hosting on request ... basically, they handle mapping incoming requests using their name servers, so not much for you to do other than send an email.