# Demo: Heroku-style Review Apps

**URL:** <https://community.fly.io/t/demo-heroku-style-review-apps/5160>\
**Category:** General\
**Created:** [May 13, 2022, 10:41pm UTC](https://community.fly.io/t/demo-heroku-style-review-apps/5160 "2022-05-13T22:41:39Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![technillogue](https://avatars.discourse-cdn.com/v4/letter/t/e8c25b/32.png) [@technillogue](https://community.fly.io/u/technillogue)\
**Post date:** [May 13, 2022, 10:41pm UTC](https://community.fly.io/t/demo-heroku-style-review-apps/5160/1 "2022-05-13T22:41:39Z")

</div>

A coworker was telling me about [Heroku’s review apps](https://devcenter.heroku.com/articles/github-integration-review-apps). I thought that was pretty cool and would be totally viable with Fly, so here’s how I set that up.

 ![](https://global.discourse-cdn.com/fly1/original/3X/f/2/f272632351bd05fbc9a82a68f33649d1c74af74b.png)

> “5/5. Great PR”—satisfied code reviewers and developers

**Stateless web app demo** , using Ktor [GitHub - technillogue/fly-review-demo: A demo for setting up Heroku-style review apps with Fly and GHA](https://github.com/technillogue/fly-review-demo). It also uses Mustache for templating and Spotless for linting.

You’ll need to set `FLY_API_TOKEN` and `REVIEW_APP_SECRETS` with any secrets your app needs. When you create a pull request, this workflow runs: [fly-review-demo/.github/workflows/create-app.yml at main · technillogue/fly-review-demo · GitHub](https://github.com/technillogue/fly-review-demo/blob/main/.github/workflows/create-app.yml). It does the straightforward process of creating an app, then deploying to it with a remote builder. The only detail is a bash replace pattern for stripping invalid characters in app names. Then, for every push, we check if there’s already a review app using the exit code of `fly status –app $review_app_name`; if so, we redeploy. Finally, merging or closing the PR destroys the app.

**If you need a database** , things are a little bit more complicated. Demo for a stateful gRPC server that uses Exposed to talk to a database: [GitHub - technillogue/fly-review-db-demo: A demo for setting up Heroku-style review apps that use a database with Fly and GHA](https://github.com/technillogue/fly-review-db-demo). It also features a neat database and gRPC mocking setup.

This time, you’ll need to additionally set `REVIEW_DB_NAME`. When a PR is opened, that postgres cluster is attached to the new review app. `fly postgres attach` does its usual thing: creating database and a user with access to it, then sets a `DATABASE_URL` app secret with the password and internal networking address. Attaching saves time over creating a fresh postgres cluster for each review app, while maintaining isolation. (Also, I found `fly postgres create` to be kind of flakey. Setting up postgres at the `fly launch` prompt worked more consistently.)

**For database migrations** , If your app image can run them on its own you can just use [` release_command` in `fly.toml`](https://fly.io/docs/reference/configuration/#release_command). However, our project uses [Liquibase](https://www.liquibase.org/)’s Gradle plugin for database version control. Previously, we were using `fly proxy`, sleeping while the proxy connects, running `./gradlew liquibaseUpdate`, and then removing the wireguard peer connection.

**To run the migrations in CI** , I wanted to avoid that sleep, so I figured out that Liquibase has an offline mode (with `JDBC_DATABASE_URL=offline:postgresql`) and can output the generated SQL it would run without actually doing it. I use this with a kind of hacky move, running `fly postgres connect` to get a psql prompt on the newly created database and piping in the generated SQL followed by `\n\q` to exit the psql shell.

Things to note there:

- Dashes in the app name are converted to underscores for the database name.
- This only runs all migrations once; subsequent database changes in the same PR would have to be migrated differently

If I could consistently generate the necessary setup SQL in advance, I wouldn’t even need to bring Gradle to the CI container.

In our case with Exposed, I also needed `.replace("postgres://(\\w*):(\\w*)@(.*)".toRegex(), "jdbc:postgresql://$3?user=$1&password=$2")`

I might post a similar setup for Python apps, but hopefully it’s clear enough how to set this up for your preferred stack.

**Future directions:**

- Having three separate workflows isn’t ideal. When you first create a PR both the create-and-deploy and the deploy workflow are triggered; this can cause a race condition where the app is deployed twice unnecessarily (and makes the list of checks a little bit confusing). There might be a way to fix this with the workflow triggers. Alternatively, it could all be rolled into a single workflow that triggers on everything and checks `github.ref_name` or environmental variables to figure out if a PR was opened, otherwise if it was a push, otherwise if a PR was closed.
- Better flags for `fly postgres connect`, namely `--command` or `--file` (see [https://community.fly.io/t/feature-request-command-and-file-for-fly-pg-connect](https://community.fly.io/t/feature-request-command-and-file-for-fly-pg-connect)).
- A global `--yes` flag to skip `WARN app flag 'review-app-name' does not match app name in config file 'review-demo'` (see [Feature request: global --yes for "does not match app name in config"](https://community.fly.io/t/feature-request-global-yes-for-does-not-match-app-name-in-config/5102)).
- Sometimes `fly deploy` exits with `==> Creating release Error An unknown error occurred` and the run fails even though the deploy was successful.
- Surfacing a message in the main PR view with a link to the created app like Heroku.
- Potentially making this available as a ready-to-use Github Action.
