Video

Want to see the full-length video right now for free?

Notes

On this week's episode, Chris is joined by Boston Development Director and TDD master Josh Clayton to discuss some of the tips we have here at thoughtbot for working with Heroku-deployed applications.

The When and Why of Heroku

Why Do We Use Heroku

We use Heroku for nearly all our applications, on both internal and client projects. Heroku offloads the ops and sysadmin work typically associated with deploying web applications, and provides hosted and managed postgres database instances with all the trimmings like backups and dataclips.

The value of not having to worry about or spend time on these aspects of building out an application is invaluable, and an important piece of how we can iterate on applications and keep feature development moving at a solid pace.

When Would You Not Use Heroku

While we certainly think Heroku is the cat's meow, there are a few cases where you might want to consider an alternative hosting platform.

Sensitive Data - One potential concern with Heroku is data security. If you're working with sensitive personal data such as health records, you might be required to use a non-hosted solution in place of Heroku. Even in these cases, it may be possible to alter just the DB or tweak a few settings and be able to continue using Heroku. Amazon has a Paper on HIPAA Compliance on AWS, and since Heroku is built on top of AWS, the tips would likely carry over.

High Cost Applications - Another commonly cited reason for not using Heroku is the cost. There is certainly some overhead in cost associated with the services Heroku provides, but in the vast majority of cases this pales in comparison to the cost and complexity of having a dedicated ops person or team. In general we try to stay on Heroku for as long as possible, and only transition away when the costs are consistently at a level that would warrant additional team members.

Parity Shell Commands

Parity is a set of shell commands that wrap the heroku command line utility, mapping to the common production, staging and development environments. Parity works by translating a given command, for instance production console into the equivalent heroku command, targeting the desired Heroku remote, heroku run console -r production in this case.

Parity requires that your remotes are named "production" and "staging" respectively, but otherwise just works. In addition, it provides shortcuts for many commands. For instance, production migrate will expand to heroku run rake db:migrate -r production. How nice!

Copying Production Databases

One particularly useful feature of parity is the ability to easily create a backup of your production data and restore it to either staging or development. This is done with the production backup and {development,staging} restore production pair of Parity commands.

As nice as these are, I wanted to streamline the two steps so I built the following shell function to automate it, including zsh tab completion of either "staging" or "development" as the argument to the function:

copy-production-to() {
  if [ "$1" != "staging" ] && [ "$1" != "development" ]; then
    echo >&2 "Usage: copy-production-to <staging|development>"
    return 1
  else
    production backup && "$1" restore production
  fi
}

# Provide tab completion of either "staging" or "development"
_copy-production-to() { reply=(development staging) }
compctl -K _copy-production-to copy-production-to

With that in my ~/.zshrc, I can just run copy-production-to development and a fresh backup will be taken and loaded into my local database. This is especially handy when combined with the next tip where you want to make some changes to live data, but would prefer to test locally before trying it on production.

Running One-off Scripts on Heroku

Often I'll have a one-off task like cleaning up some bad data, populating new data, or other similar operations that I need to perform against production. These will only be run once and are specific to the current data, so they don't belong in a rake task or migration.

I like to build and run these tasks as one off operations. It's likely not a great idea to just try out these commands directly on production, so instead, so I use the following workflows to validate a script locally before running it against production.

Executing via VTR

The primary mechanism I use, especially when the changes are not terribly complex, is to write a script in Vim, using the [vim-tmux-runner][] plugin to interactively send lines from Vim to an adjacent tmux pane running a local development console. Once I've verified the script by running locally, I'll open up a production console and re-run the lines against it. This is a lightweight approach, but still very reliable.

Check out the [section in our Tmux Trail on vim-tmux-runner][] for a detailed overview of how to use the plugin.

[vim-tmux-runner]: https://github.com/christoomey/vim-tmux-runner [section in our Tmux Trail on vim-tmux-runner]: https://upcase.com/videos/tmux-vim-integration#vim-tmux-runner

Staging Scripts on Gist

Occasionally I will have a larger or more complex script that I would prefer not to have to run via vim-tmux-runner's line sending functionality. For these, I'll compose the script locally and test it via rails runner <scriptname>. Once I have the script ready and verified, I'll save it to a [gist][] and copy the "raw" URL for the gist.

From there, I can use a combination of Heroku's remote bash shell, curl, and rails runner to execute the script on production. In practice this is very straightforward and looks like:

# run this command in your local project repo
$ production run bash

# running in the remote bash terminal opened with the command above
~ $ curl -O <raw-gist-url>
~ $ bin/rails runner <gist-name.rb>

When I run the production run bash command, Heroku will spin up a new dyno. Note this is not the dyno serving our web app, but instead is an additional dyno created just to run the bash shell. The dyno is connected to the production database and already configured to run in the production environment making this workflow otherwise very straightforward.

[gist]: https://gist.github.com/

Suspenders Heroku Features

[suspenders][] is our Rails app builder that configures a whole number of defaults and settings for all new Rails apps. As part of this, there are a few Heroku-related features that are worth reviewing.

[suspenders]: https://github.com/thoughtbot/suspenders

Rack Canonical Host

[Rack canonical host][] is a gem that introduces a piece of Rack middleware into the Rails stack, redirecting all traffic to a specified "canonical" host. This is especially useful on Heroku where your app may have a default hostname like "tranquil-blunderbuss-3348.herokuapp.com" or similar. Likewise, rack canonical host can be used to redirect between subdomains, for instance sending all requests targeting "www.yourapp.com" to "yourapp.com".

You can see the [Rack canonical host PR][] in the suspenders repo for a bit of the discussion and explanation for why we added it.

[Rack canonical host]: https://github.com/tylerhunt/rack-canonical-host#rack-canonical-host [Rack canonical host PR]: https://github.com/thoughtbot/suspenders/pull/565

Rack Timeout

[rack-timeout][] is another useful bit of middleware that is especially handy on Heroku. rack-timeout will wrap all requests and will abort any that take longer than a specified time (15 seconds is the default). This is especially important on Heroku as their routing layer will terminate the request and send a response indicating an error for long-running responses, but the server will continue processing the request to completion. This failure mode can cascade, causing your app to become unresponsive.

Again, feel free to check out the [Rack timeout PR to suspenders][] for more of the back story.

[Rack timeout PR to suspenders]: https://github.com/thoughtbot/suspenders/pull/144 [rack-timeout]: https://github.com/heroku/rack-timeout

Beta Heroku Features

There are two new features that are part of Heroku's beta program that are particularly interesting.

Review apps

[Heroku review apps][] will automatically build all pull requests on Heroku, giving you a live, production-like instance to test with. No need for reviewers to pull down your code locally to see what changes look or feel like, they will already have a live version to poke at.

This feature builds off of Heroku's app.json configuration file allowing you to specify addons as well as build commands for things like populating sample data.

[Heroku review apps]: https://devcenter.heroku.com/articles/github-integration-review-apps

Pipelines

[Heroku Pipelines][] allow you to "promote" builds from one environment to another. Imagine you deploy a new feature to staging and the product team reviews and approves it. You can then promote that build directly to production, saving on compile time and ensuring that the exact code that was approved on staging is moved to production.

[Heroku Pipelines]: https://devcenter.heroku.com/articles/labs-pipelines

bin/deploy For Common Deploy Steps

As part of all new suspenders apps, we generate a script called bin/deploy. This script ensures that all developers working on an app deploy it the same way.

The [bin/deploy in suspenders][] is reasonably lightweight, only pushing, migrating, and restarting the app. By contrast, the [bin/deploy in Upcase][] has grown considerably over the years as we've added additional functionality related to our CI, watching the Heroku processes, and opening the app in the browser.

Regardless of the specific steps, we think it's a great idea to have a common script for deployments, stored in the repo alongside the code. If you're not using one already, consider pulling in the simple version from suspenders as a start.

[bin/deploy in suspenders]: https://github.com/thoughtbot/suspenders/blob/master/templates/bin_deploy [bin/deploy in Upcase]: https://github.com/thoughtbot/upcase/blob/master/bin/deploy

Use a Staging Environment

At thoughtbot we make sure to have a staging environment on every app. Staging provides an environment as close to production as possible for verifying functionality and is an invaluable part of our rapid development and deployment workflow. Heroku, thankfully, makes this incredibly easy (and usually free).

We recommended standing up a staging app from day one. In fact, suspenders will automatically create both production and staging when you first build the app if you opt into the Heroku support. If you need to create a staging instance at a later point, Heroku provides a [fork command][] to create a copy of production.

On the topic of keeping staging as close to production as possible, we can see that the [Upcase staging environment file][] simply requires the production file, then modifies mail related settings. Taking this even further, it's possible to configure both production and staging to use the production environment config file, relying only on ENV variables to configure unique aspects of the instances.

[fork command]: https://devcenter.heroku.com/articles/fork-app [Upcase staging environment file]: https://github.com/thoughtbot/upcase/blob/master/config/environments/staging.rb

The 12 Factor App

[The 12 Factor App][] is a sort of manifesto for the Heroku approach to hosting applications, written by Heroku co-founder and former CTO, [Adam Wiggins][]. It covers the 12 major architectural and philosophical "factors" that typify a modern web application.

Many of these will be automatic for developers who've been using Heroku for some time, but the list is certainly worth revisiting from time to time to remind us of these ideas.

(You can hear an interview Ben did with Adam on the [Giant Robots podcast][].)

[The 12 Factor App]: http://12factor.net/ [Adam Wiggins]: http://about.adamwiggins.com/ [Giant Robots podcast]: http://giantrobots.fm/104

Data Clips

The last feature we want to discuss is [Heroku's Dataclips][]. Dataclips allow you to build and run one-off SQL queries against your production database. The queries can then be saved and run any time you'd like, and the results can be requested via a handful of formats including JSON, CSV, and XLS to make consuming them programmatically a breeze.

Dataclips are great for lightweight analytics or usage data, even connecting to external systems like Google Docs.

[Heroku's Dataclips]: https://devcenter.heroku.com/articles/dataclips