Want to see the full-length video right now for free?
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.
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.
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 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!
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.
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.
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
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][] 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][] 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][] 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
There are two new features that are part of Heroku's beta program that are particularly interesting.
[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
[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
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
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][] 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
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