This article goes over how to create a Heroku app when a GitHub pull request (PR) is opened:
Problem
Due to an incident that caused Heroku to revoke their GitHub integration, I needed an alternative way to create review apps without having to manually deploy them via the CLI.
Solution
I created a GitHub Actions workflow that creates a Heroku app when a PR is opened.
First, create the workflow file:
mkdir -p .github/workflows/ && touch .github/workflows/heroku-pull-request.yml
The workflow will be triggered on pull request:
name: Heroku Pull Request
on: pull_request
By default, the pull_request
event is triggered by the activity types:
- opened
- synchronize
- reopened
To also trigger the workflow with a type like closed
, you’ll need to add it:
name: Heroku Pull Request
on:
pull_request:
types: [opened, synchronize, reopened, closed]
Create a job and set the Heroku app name as an environment variable:
jobs:
heroku-pull-request:
runs-on: ubuntu-latest
env:
HEROKU_APP_NAME: my-app-pr-${{ github.event.number }}
steps:
# ...
github.event.number
is the PR number.
For the first job step, checkout the repository at the source (PR) branch:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ github.head_ref }}
Login to Heroku so you can perform actions with the CLI:
- name: Login to Heroku
uses: akhileshns/[email protected]
with:
heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
heroku_email: [email protected]
heroku_app_name: ${{ env.HEROKU_APP_NAME }}
justlogin: true
Set the Heroku API key in your repository secrets.
Create a Heroku app if the PR is opened:
- name: Create Heroku app
if: github.event.action == 'opened'
run: heroku apps:create ${{ env.HEROKU_APP_NAME }}
To create a Heroku app under a team, set the
--team
argument.
Optionally, you can add the app to an existing Heroku pipeline:
- name: Add Heroku app to pipeline
if: github.event.action == 'opened'
run: heroku pipelines:add my-pipeline --app=${{ env.HEROKU_APP_NAME }} --stage=development
Replace
my-pipeline
with your pipeline name.
Optionally, you can copy environment variables (config vars) from another Heroku app:
- name: Copy environment variables to Heroku app
if: github.event.action == 'opened'
run: |
heroku config --shell --app=my-development-app > .env
cat .env | tr '\n' ' ' | xargs heroku config:set --app=${{ env.HEROKU_APP_NAME }}
Replace
my-development-app
with your other Heroku app name.
Add the Heroku remote to the repository:
- name: Add Heroku remote
run: heroku git:remote --app=${{ env.HEROKU_APP_NAME }}
This creates a Git remote named
heroku
.
Push the local repository branch to the Heroku remote to deploy the app:
- name: Push to Heroku
run: git push heroku ${{ github.head_ref }}:master --force
Alternatively, you can replace
master
withmain
.
Optionally, you can add a PR comment after the app is deployed:
- name: Add comment to PR
if: github.event.action == 'opened'
run: |
gh pr comment ${{ github.event.number }} --body '[Heroku app](https://dashboard.heroku.com/apps/${{ env.HEROKU_APP_NAME }}): https://${{ env.HEROKU_APP_NAME }}.herokuapp.com'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
If your PR is closed, then destroy the Heroku app:
- name: Destroy Heroku app
if: github.event.action == 'closed'
run: heroku apps:destroy --app=${{ env.HEROKU_APP_NAME }} --confirm=${{ env.HEROKU_APP_NAME }}
Workflow
Here’s the full GitHub Actions workflow:
# .github/workflows/heroku-pull-request.yml
name: Heroku Pull Request
on:
pull_request:
types: [opened, synchronize, reopened, closed]
jobs:
heroku-pull-request:
runs-on: ubuntu-latest
env:
HEROKU_APP_NAME: my-app-pr-${{ github.event.number }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: ${{ github.event.action == 'closed' && 1 || 0 }}
ref: ${{ github.event.action != 'closed' && github.head_ref || '' }}
- name: Login to Heroku
uses: akhileshns/[email protected]
with:
heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
heroku_email: [email protected]
heroku_app_name: ${{ env.HEROKU_APP_NAME }}
justlogin: true
- name: Create Heroku app
if: github.event.action == 'opened'
run: heroku apps:create ${{ env.HEROKU_APP_NAME }} # --team=my-team
- name: Add Heroku app to pipeline
if: github.event.action == 'opened'
run: heroku pipelines:add my-pipeline --app=${{ env.HEROKU_APP_NAME }} --stage=development
- name: Copy environment variables to Heroku app
if: github.event.action == 'opened'
run: |
heroku config --shell --app=my-development-app > .env
cat .env | tr '\n' ' ' | xargs heroku config:set --app=${{ env.HEROKU_APP_NAME }}
- name: Add Heroku remote
run: heroku git:remote --app=${{ env.HEROKU_APP_NAME }}
- name: Push to Heroku
run: git push heroku ${{ github.head_ref }}:master --force
- name: Add comment to PR
if: github.event.action == 'opened'
run: |
gh pr comment ${{ github.event.number }} --body '[Heroku app](https://dashboard.heroku.com/apps/${{ env.HEROKU_APP_NAME }}): https://${{ env.HEROKU_APP_NAME }}.herokuapp.com'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Destroy Heroku app
if: github.event.action == 'closed'
run: heroku apps:destroy --app=${{ env.HEROKU_APP_NAME }} --confirm=${{ env.HEROKU_APP_NAME }}