Release Phase
Last updated 11 February 2020
Table of Contents
Release phase enables you to run certain tasks before a new release of your app is deployed. Release phase can be useful for tasks such as:
- Sending CSS, JS, and other assets from your app’s 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 your current release unaffected.
When using release phase, there are a number of design considerations to take in to account, especially if performing database migrations. The release phase has a 1-hour timeout, and this limit cannot be extended.
Specifying release phase tasks
To specify the tasks to run during release phase, define a release
process type in your app’s Procfile. If you’re deploying Docker images to Heroku, learn more about using release phase with Container Registry.
In this Procfile example, release
executes a Django database migration:
release: python manage.py migrate
web: gunicorn myproject.wsgi --log-file -
In this example, release
runs a script that includes a number of different commands:
release: ./release-tasks.sh
web: gunicorn myproject.wsgi --log-file -
When does the release
command run?
The release
command runs in a one-off dyno whenever a new release is created, unless the release is caused by changes to an add-on’s config vars. All of the following events create a new release:
- A successful app build
- A change to the value of a config var (unless the config var is associated with an add-on)
- A pipeline promotion
- A rollback
- A release via the platform API
- Provisioning a new add-on
App dynos do not boot for a new release until release phase finishes successfully:
You can use the heroku ps
command to see your release
command running.
You can set the dyno type using heroku ps:type release=<type>
. However, you can’t set the type before the first release phase run.
Release command failure
If the release
command exits with a non-zero exit status, or if it’s shut down by the dyno manager, the release fails. In this case, the release is not deployed to the app’s dyno formation. You will receive an email notification in the event of a release phase failure.
If a release is triggered by a change to the value of a config var, the config var value remains changed even if the release
command fails.
It is possible for a build to succeed and its associated release to fail. This does not clear the build cache.
A failed release
command usually requires a fix to your app’s code. After making the necessary 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 might be unavailable during release phase. In such occurrences, you can use the releases-retry CLI plugin to retry a failed release without needing to trigger a new build on the app.
Checking release status & logs
To check on the status of a release, including failed releases and releases that are 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
...
Use the heroku releases:output
command to see the output of a particular release
command:
$ 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 from the Heroku 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).
Canceling a release command
To cancel a release
command, first identify 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)
Then, provide the name of the one-off dyno to the heroku ps:stop
command:
$ heroku ps:stop release.5129
Review apps and the postdeploy script
Review apps run the code in any GitHub pull request in a complete, disposable Heroku app. Review apps support a postdeploy script, which is used to run one-time setup tasks.
The following timeline illustrates the order of operations and a recommended division of tasks when using both release phase and a postdeploy script for review apps.
With each new pull request, review app creation begins.
- After a successful build, the
release
command runs. This is recommended for:- Database schema setup and migrations
- CDN uploads
- Cache invalidation and warmup
- If release phase fails, review app creation also fails
After release phase succeeds:
- The postdeploy script runs. This is recommended for one-off tasks, such as:
- Setting up OAuth clients and DNS
- Loading seed or test data into the review app’s test database
On any subsequent changes to the pull request:
- The
release
command runs again. - The postdeploy script does NOT run again. The postdeploy runs only once, on creation of the review app.
The recommendations in this section also apply to Deploy to Heroku button apps that use a postdeploy script.
Design considerations
Minimise the time a failed release is left as the most recent release
In the case where a build has succeeded but the release phase failed, the built slug will be used by any later releases triggered by add-on config var changes. As those releases do not run release phase this can mean application code being deployed without release phase tasks it depends on (database migrations, for example) being run. We recommend any release phase failures are resolved as soon as possible to avoid this (for example by temporarily rolling back to a previous release).
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.