How to Automate Creating and Destroying Pull Request Review Phoenix Applications on Fly.io

By Amos Kibet , Software Developer

8 min read

This guide explains how to automate the process of creating and destroying Phoenix applications for pull request reviews on Fly.io.

As developers, we understand the importance of code review in ensuring the quality of our code. However, when we create new pull requests, reviewers sometimes need to run the app locally to see the changes. This makes it impossible for non-developers to review the work.

One solution is to have each developer manually create an app on Fly.io for each pull request they make. However, this process takes time and developers often forget to remove the apps when they finish working on the pull request.

Fly.io is a platform that allows developers to easily create and destroy review applications for each pull request. The platform has a GitHub action that makes it easy to automate the whole process. It can be found here: https://github.com/superfly/fly-pr-review-apps. In this post, we are going to learn how to automate this process.

While you can use the action as-is, we forked and made some improvements on it, which I will discuss in this post.

Optimum BH’s GitHub Action

To better suit our use case, we made several improvements to our fork of Fly.io's GitHub action:
  • We now create databases and volumes only for apps that require them.
  • When an app is destroyed, we also destroy any associated resources (databases and volumes).
  • We can import any runtime secrets that our Phoenix app requires by using the secrets keyword. Read along to learn how to do this.

To determine if an app requires a database, we wrote a script that searches for the migrate script in the app's source code. This script is typically found in the rel/overlays/bin directory. If the migrate script is found, the action will create and attach a database to the app. The APP and APP_DB variables, which represent the app's name and database name respectively, are declared elsewhere in the GitHub action’s source code.
if [ -e "rel/overlays/bin/migrate" ]; then
  # only create db if the app launched successfully
  if flyctl status --app "$APP"; then
    if flyctl status --app "$APP_DB"; then
      echo "$APP_DB DB already exists"
    else
      flyctl postgres create --name "$APP_DB" --org "$ORG" --region "$REGION" --vm-size shared-cpu-1x --initial-cluster-size 4 --volume-size 1
    fi
    # attach db to the app if it was created successfully
    if flyctl postgres attach "$APP_DB" --app "$APP" -y; then
      echo "$APP_DB DB attached"
    else
      echo "Error attaching $APP_DB to $APP, attachments exist"
    fi
  fi
fi

To determine if the app requires volumes, the script below looks for [mounts] in the config file.
if grep -q "\[mounts\]" fly.toml; then
  # create volume only if none exists
  if ! flyctl volumes list --app "$APP" | grep -oh "\w*vol_\w*"; then
    flyctl volumes create "$VOLUME" --app "$APP" --region "$REGION" --size 1 -y
  fi
  # modify config file to have the volume name specified above.
  sed -i -e 's/source =.*/source = '\"$VOLUME\"'/' "$CONFIG"
fi

First, we need to check if the app already has a volume. If we do not perform this check, multiple volumes will be created. While this is not necessarily problematic, it is wasteful. Also, we need to modify the config file to include the new volume name. If we neglect this step, the deployment will fail.

Automating the Creation of Review Applications

Now, here's an example of how we can set up a workflow to automatically create a review application for each pull request.

For the workflow to work, you need to put FLY_API_TOKEN , generated by running flyctl auth token under GitHub repository or organization secrets.
name: Review App
on:
  pull_request:
    types: [opened, reopened, synchronize]
env:
  FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
  FLY_ORG: Personal
  FLY_REGION: jn
  REPO_NAME: sample-app
jobs:
  deploy_review_app:
    name: Create & Deploy Review App
    runs-on: ubuntu-latest
    # Only run one deployment at a time per PR.
    concurrency:
      group: pr-${{ github.event.number }}
    # Create a GitHub deployment environment per review app
    # so it shows up in the pull request UI.
    environment:
      name: pr-${{ github.event.number }}
      url: https://pr-${{ github.event.number }}.${{ env.REPO_NAME }}.fly.dev
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Create & Deploy Review App
        id: deploy
        uses: optimumBA/fly-preview-apps@main
        with:
          name: pr-${{ github.event.number }}-${{ env.REPO_NAME }}

You can import any secrets that your Phoenix application requires to run. After adding the secrets to your application's GitHub repository, you can access them in your workflow using secrets keyword.
- name: Create & Deploy Review App
  id: deploy
  uses: optimumBA/fly-preview-apps@main
  with:
    name: pr-${{ github.event.number }}
    secrets: 'SECRET_1=${{ secrets.YOUR_SECRET_1 }} SECRET_2=${{ secrets.SECRET_2 }}\nSECRET_n=${{ secrets.SECRET_n }}'

For every successful deployment, GitHub actions will set environment and deployment variable variables, pointing to the name and url of the deployed review application. You can find them under Environments tab in your application’s GitHub repository.

Automating the Destruction of Review Applications

After the pull request has been merged or closed, the review application is no longer needed. Here is an example of a workflow that automatically destroys the review application:
name: Delete Review App
on:
  pull_request:
    types:
      - closed
env:
  FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
  REPO_NAME: sample-app
jobs:
  delete_review_app:
    runs-on: ubuntu-latest
    name: Delete Review App
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Delete Review Deployment
        uses: optimumBA/fly-preview-apps@main
        with:
          name: pr-${{ github.event.number }}-${{ env.REPO_NAME }}

Bonus

For every deployment, the workflow also creates environments on GitHub. These environments display the name and live link of the deployed review app. It is important to note that the workflow we created to delete the deployments once the pull request is closed only destroys resources on Fly.io, and does not remove the GitHub environments.

To remove these GitHub environments, we will extend our workflow using third-party GitHub actions. These actions require an auth token in order to delete the environments. The available GitHub token (available as `secrets.GITHUB_TOKEN` in the workflow) does not have enough permissions to delete GitHub environments. To proceed, we need to create a GitHub app and grant it the following permissions, under repository permissions:
  • Actions: Read
  • Administration: Read & Write
  • Deployments: Read & Write
  • Environments: Read & Write
  • Metadata: Read
Read more on these permissions on https://docs.github.com/en/rest/overview/permissions-required-for-github-apps?apiVersion=2022-11-28, and steps to create a GitHub app on https://docs.github.com/en/apps/creating-github-apps/creating-github-apps/creating-a-github-app.

Please refer to the respective documentation of these GitHub actions for more information on additional setup steps:
Here is a complete workflow that deletes both deployments and GitHub environments:
name: Delete Review App
on:
  pull_request:
    types:
      - closed
env:
  FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
  REPO_NAME: sample-app
jobs:
  delete_review_app:
    runs-on: ubuntu-latest
    name: Delete Review App
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Delete Review Deployment
        uses: optimumBA/fly-preview-apps@main
        with:
          name: pr-${{ github.event.number }}.${{ env.REPO_NAME }}
      - name: Get Token
        uses: navikt/github-app-token-generator@v1.1.1
        id: get-token
        with:
          app-id: ${{ secrets.GH_APP_ID }}
          private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
      - name: Delete GitHub Environments
        uses: strumwolf/delete-deployment-environment@v2.2.3
        with:
          token: ${{ steps.get-token.outputs.token }}
          environment: pr-${{ github.event.number }}
          ref: ${{ github.head_ref }}

Conclusion

Using pull request review applications can greatly improve the efficiency of the code review process. By automating the creation and destruction of these applications, we can save time and ensure that our code is thoroughly reviewed before it is merged into our codebase. This approach also saves on resources. 
With the help of GitHub Actions and Fly.io, automating this process is easy and straightforward.

More articles