Release Phase
Last updated 01 November 2017
Table of Contents
Release phase enables you to run tasks before a new release is deployed to production. You may find release phase useful for tasks such as:
- Sending CSS, JS, and other assets from the slug to a CDN or S3 bucket
- Priming or invalidating cache stores
- Running database schema migrations
If a release phase task fails the new release is not deployed, leaving the production release unaffected.
When using release phase, there are a number of design considerations to take in to account, especially if migrating a database.
Getting started
To get started, define a release process type in your Procfile as well as the command you want to run.
In this Procfile example, release executes a Django database migration:
release: python manage.py migrate
web: gunicorn myproject.wsgi --log-file -
In this Procfile example, release runs a script with a number of different commands:
release: ./release-tasks.sh
web: gunicorn myproject.wsgi --log-file -
When does the release command run?
The release command is run when a new release is created. That means it will run after:
- An app build
- A config var set by a user
- A pipeline promotion
- A rollback
- A release via the platform API
The app dynos will not boot on the new release until release phase finishes successfully.

When is the release command skipped?
Add-ons can create new releases when they need to change the value of the config vars they manage. For example, the Heroku Postgres add-on may perform a failover of the database, changing the hostname value.
Releases created automatically by add-ons will not run the release command. Those releases will be marked as successful without running the release command.
When a release command fails
If the release command exits with a non-zero exit status, or if it’s shut down by the dyno manager, the release will fail and will not be deployed to the app’s formation. You will receive an email notification on release phase failure.
An important note about config variables: if the release includes new or updated config variables they will be set, even if the release command fails. A failed release will not roll back config variables.
It is possible for a build to be successful and a release to fail, which will not clear the build cache.
Checking release status & logs
To check on the status of a release, including failed releases and those pending due to a long running release command, run heroku releases.
$ heroku releases
=== limitless-savannah-19617 Releases - Current: v52
v53 Deploy ad7c527 release command failed jbyrum@heroku.com
v52 Deploy b41eb7c jbyrum@heroku.com
v51 Deploy 38352d3 jbyrum@heroku.com
...
You can see the output of a release command by running:
$ heroku releases:output RELEASE_NUMBER
--- Migrating db ---
INFO [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO [alembic.runtime.migration] Will assume transactional DDL.
Release logs are also available on the dashboard:

Checking release status programmatically
To check the status of a release programmatically, you can curl the Platform API for a specific release, or list all releases (see Platform API documentation for more detail). In the following example, the status key has a value of failed:
$ curl -n https://api.heroku.com/apps/<app_id_or_name>/releases/ \
-H "Accept: application/vnd.heroku+json; version=3"
{
"app":{
"id":"a933b6af-b2e3-4a03-91a9-1c758110a553",
"name":"limitless-savannah-19617"
},
"created_at":"2016-07-29T22:43:20Z",
"description":"Deploy ad7c527",
"status":"failed",
"id":"735a9e8c-ef49-4047-8079-984d40a84051",
"slug":{
"id":"a49ed40f-801a-45ff-8b90-758c5a33637f"
},
"updated_at":"2016-07-29T22:43:20Z",
"user":{
"email":"jbyrum@heroku.com",
"id":"cbcd34ce-c556-4289-8bc7-2dc160649fb7"
},
"version":53,
"current":true,
"output_stream_url":"https://release-output.heroku.com/streams/a9/..."
}
Output of the release is available under the output_stream_url attribute and can be retrieved programmatically by making a GET request on the URL.
The output is sent via chunked encoding and the connection is closed when the command completes.
If a client connects after data is sent for a given URL, the data is buffered from the start of the command. Output can be streamed while the command is in progress, and at any time after it has completed (in the latter case, all output will be sent immediately).
Handling failed releases
When a release command fails, this usually requires a fix in the app’s code. After making the required changes, push the new code to trigger a new release.
In some cases, a release failure is unrelated to the app’s code; for example, an external service is unavailable during release phase. In such occurrences, you can use the releases-retry CLI plugin to retry a failed release without the need to trigger a new build on the app.
Canceling a release command
To cancel a release command, you should find the one-off dyno executing the command:
$ heroku ps
=== release (Free): bundle exec rake db:migrate
release.5129: up 2016/02/01 12:17:45 (~ 2s ago)
Now terminate the dyno, substituting the dyno name:
$ heroku ps:stop release.5129
Review apps and postdeploy script
Review apps run the code in any GitHub pull request in a complete, disposable app environment on Heroku. They are a great way to propose, discuss, and merge changes to your code base.
One feature offered by review apps is the postdeploy script, which is used to run any one-time setup tasks.
The following section offers guidance if using both release phase and a postdeploy script in your review apps.
With a new pull request, review app creation begins:
- Release phase is run (after a successful build), which is recommended for:
- Database schema setup and migrations
- CDN uploads
- Cache invalidation and warm up
- If release phase fails, review app creation will fail
After release phase is successful:
- Postdeploy script is run, which is recommended for one-off tasks:
- Setting up OAuth clients and DNS
- Loading seeds/test data into the review app’s test database
On changes to a pull request:
- Release phase is rerun
- Postdeploy script is NOT run (postdeploy is run once on review app creation)
The recommendations in this section also apply to Deploy to Heroku button apps using the postdeploy script.
Design considerations
Not suggested for asset compilation or other tasks requiring filesystem persistence
Release phase is not suitable for tasks that require filesystem persistence, as filesystem changes during release phase will not be deployed to your app’s dyno formation (the dyno filesystem is ephemeral). Asset compilation should happen during builds. Release phase can be used to upload the compiled assets to a CDN.
The following considerations are primarily relevant when creating manual database migration scripts. If you are using an ORM, such as ActiveRecord, these considerations may not apply. Learn more about database best practices.
Use transactions for database migrations
If you perform a database migration, you should always use transactions. A transaction ensures that all migration operations are successful before committing changes to the database. This minimizes the possibility of a failed partial migration during release phase. If a database migration fails during release phase (i.e., the migration command exits with a non-zero status), your new release will not be deployed. If transactions were not used, this can leave your database in a partially migrated state. We suggest using heroku run, rather than release phase, to correct your schema/data.
Check whether a database has already been migrated, before executing a migration
Many actions create a new release, such as a setting a config var or adding a new addon to your app. Your database migration script should check whether a database has already been migrated, before executing a new migration (e.g., does table/column exist, if not add it). This will prevent a new release – such as one created from a new config var – from rerunning your database migration.
Before running a migration, grab an advisory lock on the database
Heroku releases can run concurrently, which can be a problem if release phase is executing a database migration. Many popular relational databases, such as Postgres and MySQL, offer advisory lock functionality, which can be used to prevent concurrent migrations. Advisory locks are application enforced database locks; when acquired, your tables are not locked for writing, so your application will continue to behave normally.
In Postgres, before running your migration, you can acquire an advisory lock. In the example below we try to get an advisory lock with the key migration:
SELECT pg_try_advisory_lock(migration);
If the lock is successful, Postgres will return t. Now it should be safe to run your migration. If unsuccessful, f is returned and you can fail your migration.
If the advisory lock is acquired within a transaction, it will be automatically released when the transaction is committed. You can also release a lock by calling:
SELECT pg_advisory_unlock_all();
or
SELECT pg_advisory_unlock(key);
Known issues
- It is possible for a release command to fail due to an add-on not being fully provisioned when the command is run; for example, you’re trying to run a db migration, but the db add-on isn’t finished with provisioning. Both Heroku Postgres and Heroku Redis are NOT impacted by this issue.