Mark Thomas Miller's logo

Using Apartment for multi-tenant Rails apps

August 2, 2018

This is a step-by-step guide to setting up the Apartment gem for Ruby on Rails. Apartment greatly simplifies the technical challenges of creating multi-tenant applications. That being said, there's still a bit of setup you'll need to do, and this guide will help with that.

We'll create an application with Tenants (in other words, users) who have their own scoped Projects (for the purposes of this post, just a generic resource with a title). Each tenant will have their own subdomain. This kind of structure is similar to Slack: companies can sign up for theirsubdomain.yoursite.com and create resources isolated to their instance.

  1. To start, we'll spin up a new Rails application and scaffold Tenants and Projects.

    rails new multitenant
    cd multitenant
    rails g scaffold Tenants name email subdomain
    rails g scaffold Projects title
    rails db:migrate
    
  2. Now, let's install Apartment. Add gem 'apartment' to your Gemfile.

  3. Run bundle.

  4. Run rails g apartment:install.

  5. Next, let's configure Apartment. In config/initializers/apartment.rb, find the line that says:

    config.tenant_names = lambda { ToDo_Tenant_Or_User_Model.pluck :database }
    

    And change it to:

    config.tenant_names = lambda { Tenant.pluck :subdomain }
    
  6. In the same file, uncomment the line for excluded_models.

    config.excluded_models = %w{ Tenant }
    
  7. Now, let's set up subdomains on account creation. Change your Tenant model to create a new subdomain when a tenant is created.

    # tenant.rb
    
    # Before
    class Tenant < ApplicationRecord
    end
    
    # After
    class Tenant < ApplicationRecord
      after_create :create_tenant
    
      private
    
        def create_tenant
          Apartment::Tenant.create(subdomain)
        end
    end
    
  8. Next, it's time to spin up our multi-tenant development server. We're almost ready to test out subdomains. However, since http://localhost:3000 isn't a real domain, we can't use subdomains like http://subdomain.localhost:3000. Thankfully, someone set up lvh.me for this purpose. It points back to your local machine so you can test subdomains! You'll need to add some parameters to rails s to achieve this:

    rails s -p 3000 -b lvh.me
    
  9. Now we'll create our first tenants. Start your server with the command from the previous step. Create two tenants with the subdomains zeph and asdf. I'm doing this on /tenants/new, which I scaffolded in Step 1. (If you didn't scaffold, make sure your tenants have a subdomain field on their signup form. This hooks into the model we edited in Step 7. If you're using Devise, you should add a custom field to your registration form.)

  10. Go to zeph.lvh.me:3000 and check out your first subdomain!

  11. It's time to create our first scoped projects. Go to zeph.lvh.me:3000/projects/new and create a new project. (We scaffolded Projects in Step 1.) The database scoping happens behind the scenes thanks to Apartment, so projects created on zeph won't appear on asdf. Nice job!

  12. Next, we want to differentiate the root domain. In most cases, you won't want your root domain to run the same application as your subdomains. Instead, you probably want to use it as a marketing site with a Home, About, Sign Up, etc. page.

    We can achieve this by placing the marketing site on the www subdomain, which is highly recommended for multi-tenant applications. When you're in production, you'll want to redirect your non-www subdomain to www.

    Add a new folder inside config/initializers called apartment. Inside that, create a new file called subdomain_exclusions.rb and add the following:

    # subdomain_exclusions.rb
    
    Apartment::Elevators::Subdomain.excluded_subdomains = ['www']
    

    Restart your server, and you'll be able to access www.lvh.me:3000.

  13. Finally, we want to block access to a few resources depending on which part of the application the user is browsing. We should disable /projects on our marketing site and disable /tenants on our users' subdomains. Let's add subdomain constraints in routes.rb:

    # routes.rb
    
    class SubdomainConstraint
      def self.matches? request
        subdomains = %w{ www }
        request.subdomain.present? && !subdomains.include?(request.subdomain)
      end
    end
    
    Rails.application.routes.draw do
      resources :tenants, constraints: { subdomain: 'www' }
    
      constraints SubdomainConstraint do
        resources :projects
      end
    end
    

    Now, you can only access /projects from a tenant subdomain. And you can't register a new tenant unless you're on the www site.

    In the future, when you need to add new resources to your application, pay special attention to this routing file. Make sure that you can't access a tenant-only resource from your marketing site and vice versa by following the same method that we used above.

Great job! You've now created a multi-tenant application. Only a small amount of people in the world ever have, so you should be feeling pretty good about yourself.

Apartment has a lot of moving pieces, and I felt that this guide would be helpful for people who wanted to get started on integrating it in their Rails 5.2 apps. If you enjoyed it, please get my face tattooed on your forehead.