Managing Multiple Environments for an App
Last updated August 06, 2020
Table of Contents
Your Heroku app runs in at least two environments:
- On your local machine (i.e., development).
- Deployed to the Heroku platform (i.e., production)
Ideally, your app should run in two additional environments:
- Test, for running the app’s test suite safely in isolation
- Staging, for running a new build of the app in a production-like setting before promoting it
You can help prevent buggy code from being deployed to production by maintaining these separate environments as different Heroku apps, and by using each environment only for its intended purpose.
This strategy is particularly important because Heroku’s Linux stacks likely differ from your development machine’s operating system. This means you can’t be sure that the code that works in your local development environment will work exactly the same way in production.
Heroku provides helpful tools (namely pipelines, Heroku CI, and review apps) for creating and maintaining your app’s staging and test environments.
Creating and linking environments
Creating a staging environment
Let’s say you have an application running on your local (development) machine and you’re ready to push it to Heroku. You can create a new Heroku app to represent your staging environment with the Heroku CLI:
$ heroku create --remote staging
Creating strong-river-216.... done
http://strong-river-216.heroku.com/ | https://git.heroku.com/strong-river-216.git
Git remote staging added
By default, the Heroku CLI adds a Git remote named heroku
to your repo. The command above uses the --remote
flag to specify a different name for the remote (in this case, staging
). Therefore, you use the following command to push your code to your app’s new staging environment:
$ git push staging master
...
$ heroku ps --remote staging
=== web: `bundle exec puma -C config/puma.rb``
web.1: up for 21s
Creating a production environment
Once your staging app is up and running properly, you can create your production environment and push to it with the same commands:
$ heroku create --remote production
Creating fierce-ice-327.... done
http://fierce-ice-327.heroku.com/ | https://git.heroku.com/fierce-ice-327.git
Git remote production added
$ git push production master
...
$ heroku ps --remote production
=== web: `bundle exec puma -C config/puma.rb``
web.1: up for 16s
You will need to do some work to keep these apps in sync as you continue to develop them. You’ll need to add contributors, config vars, buildpacks, and add-ons to each individually, for instance.
You now have the same codebase running as two separate Heroku apps, one for staging and one for production.
Specifying an environment with the Heroku CLI
When a Git repo has more than one associated Heroku remote, the Heroku CLI’s commands require you to specify which app you’re interacting with by including the -r
(or --remote
) option. To simplify your workflow, you can edit your Git config to indicate a default Heroku remote.
For example, you can make staging
your default Heroku remote with the following command:
$ git config heroku.remote staging
This adds the following section to your project’s .git/config
file:
[heroku]
remote = staging
After you make this change, all Heroku CLI commands you run from your repo will default to managing your staging
app. To run a command against your production
app, simply include -r production
in the command.
Linking environments with pipelines
Heroku pipelines make it easy to link your app’s environments and promote code from staging to production.
Create a new pipeline from the Heroku Dashboard and add your staging and production environments to it by following these steps.
Managing staging and production configurations
Many languages and frameworks support flipping a development/production switch. For example, when in development mode, you may use a different database, have increased logging levels, and send all emails to yourself instead of to end users.
You should use RAILS_ENV=production
or RACK_ENV=production
for your staging applications to minimize surprises while deploying to production. Read Deploying to a custom Rails environment for more information.
The services and libraries that your application uses may also need their own configuration variables set, mirroring those on production. For example, you may use a different S3 bucket for staging than you do on production, so you will use different values for the keys:
$ heroku config:set S3_KEY=XXX --remote staging
$ heroku config:set S3_SECRET=YYY --remote staging
Advanced: Linking local branches to remote apps
It’s simple to type git push staging master
and git push production master
when you’ve followed the steps above. Many developers like to take advantage of git’s branches to separate in-progress and production-ready code, however. In this sort of setup, you might deploy to production from your master
branch, merging in changes from a development
branch once they’ve been reviewed on the staging app. With this setup, pushing is a littler trickier:
$ git push staging development:master
This command tells git that you want to push from your local development
branch to the master
branch of your staging
remote. (It might look a little disorderly, but there’s a lot more going on - take a look at the git book for a very in-depth exploration of refspecs.)
If you want to simplify your git commands, you can make things easier by forcing your local git branches to track your remote applications. Assuming you’ve got git remotes for staging
and production
, you can do the following:
If you want to set push.default
for all git repositories (instead of just this one), add --global
to the command.
$ git config push.default tracking
$ git checkout -b staging --track staging/master
Branch staging set up to track remote branch master from staging.
Switched to a new branch 'staging'
Now, you’re in the staging
branch and you’re set up so that git pull
and git push
will work against your staging environment without any further arguments. Change some code, commit it, and push it up:
$ git commit -a -m "changed code"
$ git push
Counting objects: 11, done.
...
Notice that you said git push
, not git push staging staging:master
. The push.default
setting is what makes this possible; with that set to tracking
, your local branches will automatically push changes to the remote repositories that they track.
If you’d like your local master
branch to point to your production
remote (and you’re running git 1.7 or later), you can do the following:
$ git fetch production
$ git branch --set-upstream master production/master
And with that, git push
from master
will update your production app on Heroku.