A Sub-domain Recipe for Heroku

Amit Narayanan
Mammoth Stories
Published in
6 min readJul 7, 2015

--

Quickly add sub-domains to a web-app

A step-by-step process

As apps grow bigger, a nice way to achieve logical and physical separation is to dice it up into sub-domains. And run each as a separate app with its own monitoring and performance thresholds. This way, the metrics for each are also split (api.yoursite.com, admin.yoursite.com, app.youresite.com, etc.) and become more fine grained. So that slow query joining several tables for a dashboard view will no longer gunk up the stats for say, APIs.

This could mean the spinning off of orthogonal things like admin features and dashboards, or separating the app’s core (say publicly available parts from private ones), or something else entirely.

Two Kinds Of Separation

Sub-domains can be either logically or physically separate depending on how it is configured.

Logical separation allows dynamically creating as many sub-domains as needed (like a sub-domain per customer) all pointing back to same DNS host.

Physically separating the app (i.e. running the app on different DNS hosts) requires setting up the actual hosts, running the app on these hosts, and configuring DNS so each sub-domain points to / resolves to the correct host.

Heroku

For Mammoth, I needed a quick, reliable, and repeatable process for separating an app into sub-domains, but couldn’t really find one. And each time I attempted it, there was always something that ended up getting missed.

Given the nature of the beast, it isn’t really possible for anyone to remember exactly all of the steps involved along with all the gory little details, so I decided to collect them into a ‘recipe’ of sorts. I hope this helps anyone trying to do the same thing on Heroku. Here we go.

Create New Heroku App

# create new heroku app to run on the new subdomainheroku apps:create SUBDOMAIN-APP-NAME

Add Heroku “SSL Add-On”

Of course, you’ll need this only if your app supports HTTPS. If not, skip this and the next.

# get the heroku "ssl add-on" for the new appheroku addons:create ssl:endpoint -a SUBDOMAIN-APP-NAME

Add SSL Certs

Point to note here is that this cert must necessarily be a wildcard cert which verifies anything under yourwebsite.com (*.yourwebsite.com).

# add wild card cert to new heroku appheroku certs:add STAR_CERT.pem STAR_CERT.key -a SUBDOMAIN-APP-NAME

DNS

Within your DNS provider’s interface, add a new CNAME record pointing to the newly generated Heroku SSL Endpoint (or the naked Heroku app endpoint if not using the Heroku SSL Add-on) in the above step.

This may need a bit of time to propagate, so continue with the rest of the config while letting this bake. We can test this at the very end.

Install Config Var Plugin

Manually copying config vars just blows. Heroku plugins to the rescue. This one makes it dead simple to copy config vars from one Heroku app to another.

Of course, you can skip this step if you already have this plugin or another one that does the same thing.

# install plugin to copy config varsheroku plugins:install https://github.com/set5think/heroku-config-copy.git

Copy Config Vars

# copy config vars to the new heroku app heroku config:copy --dst_app SUBDOMAIN-APP-NAME -a ROOT-APP-NAME

Tweak Config Vars

Once you’ve copied all config vars, you most likely want to tweak a few. At the minimum you’ll want to tweak the Heroku and NewRelic app name configurations. You may have more depending on your app.

# tweak config varsheroku config:set HEROKU_APP=SUBDOMAIN-APP-NAME NEW_RELIC_APP_NAME=SUBDOMAIN-APP-NAME -a SUBDOMAIN-APP-NAME

Set Canonical Host

# set canonical host. rack config can no longer default to 
# your-production-app.com or your-staging-app.com each physically
# separate app needs to configure it's own canonical host
heroku config:set CANONICAL_HOST=subdomain.yourapp.com -a SUBDOMAIN-APP-NAME

Configure Heroku Custom Domain

Do this to change the default Heroku custom domain (that is added automatically by Heroku when an app is provisioned) to your actual domain (yoursite.com)

# configure heroku app with custom domainheroku domains:add subdomain.yourwebsite.com -a SUBDOMAIN-APP-NAME

Configure Heroku Remote

Add a new git remote endpoint so you can push the latest code base to this new app (the one that’ll run on your sub-domain)

# add a git remote to newly created heroku app so you can 
# push code to it
git remote add SUBDOMAIN-APP-NAME https://git.heroku.com/RELEASE-BRANCH-NAME.git

Push

# push
git push SUBDOMAIN-APP-NAME master

Test DNS config

Finally we want to test if the new CNAME record that we created before with the DNS provider actually resolves to the correct host.

dig NS +short subdomain.yourwebsite.com

Somewhere in response to this command should be the new sub-domain name (CNAME) and the Heroku SSL endpoint that it points to.

blah1-1234.herokussl.com.
1234567890.us-west-1.elb.amazonaws.com.

If CNAME points to the correct Heroku SSL endpoint, we’re basically done. Try curl-ing https://subomain.yoursite.com and if it responds in the affirmative, we’re off to the races.

Local Development

This is great, but what happens to our local development environments? We could muck with our /etc/hosts file, but there’s an even easier way.

And that is to use lvh.me (local virtual host). On this, thanks are due to @levicook for registering this domain, so we don’t all have to reconfigure our computers.

SlimJim:mammoth $ whois lvh.me
Registrant Name:Levi Cook

All we really need to do is configure the HOST_NAME param in development.rb.

HOST_NAME = "lvh.me:3000"

Now, all sub-domains like admin.lvh.me:3000, api.lvh.me:3000, etc. will, by default, resolve to localhost:3000.

Alternatively, we could also use something like ngrok.

Split the App?

Since we now have functional sub-domains that can run our app, we have two options on what to do with the original code base. Ideally, we would also split up the code base so each sub-domain runs only what’s relevant. But this isn’t easy. Indeed, this is a lot of work and something deserving of it’s own post. Splitting the code base means all kinds of refactorings to extract functionality and ensuring that code isn’t duplicated while doing so. Do all common classes become their own Gems? How about models? Do they also become Gems? You see where I’m going and how it could become a can of worms.

I have an easier way. And this is to not bother separating the code base, but have the exact same app running on all the sub-domains. And instead, create more sophisticated routes with sub-domain constraints that, depending on the request, can be smartly routed to the correct sub-domain.

Here’s an example of how to always route a request to “some_request_path” to a subdomain:

get 'some_request_path', to: redirect(subdomain: 'subdomain', path: 'some_request_path')

Granted, this has the redirect ugliness which slows down the first request (the redirect makes it 2 HTTP requests instead of one), but all subsequent requests to this sub-domain will work as always. I’m all ears if someone’s got a neat solution to circumvent even the initial redirect (Hit me up!)

This way though, the code base stays the same which makes it infinitely easier to manage. It also makes deployment a cake with git connector.

Got Connectors?

Sorry, couldn’t resist. I meant Git connectors of course, which are absolutely brilliant. And here’s why.

With multiple sub-domains, we’ll need to push the latest code base to each host. But pushing the app manually to each separate host gets old really fast. But the good folk at Heroku already thought of this and added git connectors. In short, it’s a separate git branch (or repo, if you like) for (stage or production) releases that Heroku can monitor. And whenever said branch (or repo) is updated, the latest code in that branch is auto-deployed to all configured hosts. It’s simple and elegant. The best part is that the logs from the deployment is piped straight into the Heroku dashboard (under the “Activities” tab—all logging is live). Indeed, I found this sole feature alone so neat that I sent them a quick note about it (it was beyond midnight or something, so excuse my French :).

With git connector hooked up, we can now push the latest app to all hosts with a single command:

# push to all hosts with a single commandgit push STAGE-RELEASE-BRANCH master
git push PROD-RELEASE-BRANCH master

Happy sub-domaining!

I’m @amitnarayanan on Twitter.

Also, check out Mammoth, a cool app I’m currently building.

On the web and in the AppStore.

--

--