Create Heroku app on GitHub Pull Request


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 with main.

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 }}


Please support this site and join our Discord!