# Pull Request Previews and Preview Environments

**URL:** <https://community.fly.io/t/pull-request-previews-and-preview-environments/4120>\
**Category:** General\
**Created:** [February 18, 2022, 9:32am UTC](https://community.fly.io/t/pull-request-previews-and-preview-environments/4120 "2022-02-18T09:32:31Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![nahtnam](https://sea1.discourse-cdn.com/fly1/user_avatar/community.fly.io/nahtnam/32/13664_2.png) [@nahtnam](https://community.fly.io/u/nahtnam)\
**Post date:** [February 18, 2022, 9:32am UTC](https://community.fly.io/t/pull-request-previews-and-preview-environments/4120/1 "2022-02-18T09:32:31Z")

</div>

I’ve always been fascinated by Infrastructure as Code. I was wondering if this is something on fly’s radar? I know there is currently a fly.toml, but I think you can only define one app at a time in it. I think being able to define multiple apps, postgres, etc would be a cool abstraction that would then allow for duplicated the whole stack in a PR temporarily and then shutting down.

Example: [Pull Request Previews | Render](https://render.com/docs/pull-request-previews)

Side note: Is it possible to just write JSON instead of TOML?

---

<div class="post-metadata">

**Author:** ![stefan.luptak](https://sea1.discourse-cdn.com/fly1/user_avatar/community.fly.io/stefan.luptak/32/2614_2.png) [@stefan.luptak](https://community.fly.io/u/stefan.luptak)\
**Post date:** [February 18, 2022, 1:47pm UTC](https://community.fly.io/t/pull-request-previews-and-preview-environments/4120/2 "2022-02-18T13:47:19Z")

</div>

We’re using [Render.com](http://Render.com) with their IaC for Preview environments and since we have two repos (backend, frontend) it causing us issues quite often.  
I feel I would rather do it “manually” with the `flyctl` command called from GitHub actions than fighting the IaC limitations regularly. Just browse the [https://feedback.render.com](https://feedback.render.com) site. Lot of the feature requests / issues are IaC related.  
I think it’s extremely difficult to find a balance between versatility, complexity and simplicity because the use cases can be so different and complex.  
IMHO the time spent doing that would be better invested elsewhere. 🙂  
Just my two cents.

---

<div class="post-metadata">

**Author:** ![nahtnam](https://sea1.discourse-cdn.com/fly1/user_avatar/community.fly.io/nahtnam/32/13664_2.png) [@nahtnam](https://community.fly.io/u/nahtnam)\
**Post date:** [February 18, 2022, 8:00pm UTC](https://community.fly.io/t/pull-request-previews-and-preview-environments/4120/3 "2022-02-18T20:00:35Z")

</div>

It seems to me that render didn’t implement it well. Heroku is used by many large companies and it seems to work very well.

IMO, I think most of what fly can do distributed wise can be also done by AWS/GCloud, so at the moment I feel like fly is targeted to companies that need scale but can’t afford to hire someone to manage the complex infrastructure. I feel like that’s a lot smaller pool than companies looking for a simple all in one solution (that happens to scale as they grow). ¯\_(ツ)\_/¯

Maybe I’m completely wrong, just wanted to put it out there

---

<div class="post-metadata">

**Author:** ![jmill](https://sea1.discourse-cdn.com/fly1/user_avatar/community.fly.io/jmill/32/14687_2.png) [@jmill](https://community.fly.io/u/jmill)\
**Post date:** [February 18, 2022, 9:43pm UTC](https://community.fly.io/t/pull-request-previews-and-preview-environments/4120/5 "2022-02-18T21:43:30Z")

</div>

I just spent some time setting up CI in GitHub to give us a simple preview environment with Phoenix and Postgres with the `superfly/flyctl-actions` GitHub Action – if there’s some interest I can redact some of our specific info and share?

---

<div class="post-metadata">

**Author:** ![nahtnam](https://sea1.discourse-cdn.com/fly1/user_avatar/community.fly.io/nahtnam/32/13664_2.png) [@nahtnam](https://community.fly.io/u/nahtnam)\
**Post date:** [February 18, 2022, 9:51pm UTC](https://community.fly.io/t/pull-request-previews-and-preview-environments/4120/6 "2022-02-18T21:51:44Z")

</div>

That would be great!

---

<div class="post-metadata">

**Author:** ![jmill](https://sea1.discourse-cdn.com/fly1/user_avatar/community.fly.io/jmill/32/14687_2.png) [@jmill](https://community.fly.io/u/jmill)\
**Post date:** [February 18, 2022, 10:43pm UTC](https://community.fly.io/t/pull-request-previews-and-preview-environments/4120/7 "2022-02-18T22:43:26Z")

</div>

I have 2 GH workflows. The first is from their docs, I call it `main.yml`. The second is called `branch.yml`.

The key for me is in addition to the `fly.production.toml` that I use for all my deploys (using the GH Actions formula from fly’s docs, with `deploy -c fly.production.toml`), I made a copy and named it a `fly.development.toml` with the following changes:

```auto
# fly.development.toml
app = "<REPLACED_IN_CI>-dev-<myapp>"
...
PHX_HOST = "<REPLACED_IN_CI>-dev-<myapp>.fly.dev"

```

The key part is changing these _during the deployment pipeline_. I just recreated the “manual” process using the GH Action from fly. The most difficult parts were figuring out the correct order and replacing the `<REPLACED_IN_CI>` portions with `sed` 😂

```auto
# branch.yml
name: Dev Preview
on:
  pull_request:
    branches:
      - main
env:
  FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
jobs:
  infrastructure:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Github repo
        uses: actions/checkout@v2.4.0
      - name: Save short SHA to env
        uses: benjlevesque/short-sha@v1.2
        id: short-sha
      - name: Build DB
        uses: superfly/flyctl-actions@1.1
        with:
          args:
            "pg create --region atl --vm-size shared-cpu-1x --name ${{ env.SHA
            }}-dev-<myapp>-db --volume-size 1 --organization <myorg>
            --initial-cluster-size 1"
      - name: Create fly app
        uses: superfly/flyctl-actions@1.1
        with:
          args: "apps create ${{ env.SHA }}-dev-<myapp> --org <myorg>"
      - name: Connect app to DB
        uses: superfly/flyctl-actions@1.1
        with:
          args:
            "pg attach -a ${{ env.SHA }}-dev-<myapp> --postgres-app ${{ env.SHA
            }}-dev-<myapp>-db"
      - name: Set up Phoenix secret_key_base with fly secrets
        uses: superfly/flyctl-actions@1.1
        with:
          args:
            "secrets set
            SECRET_KEY_BASE=<set-a-secret-or-use-github-secrets>
            -a ${{ env.SHA }}-dev-<myapp>"
      - name: Modify fly.development.toml file
        run: |
          sed -i 's/<REPLACED_IN_CI>/${{ env.SHA }}/g' fly.development.toml
      - name: Deploy app
        uses: superfly/flyctl-actions@1.1
        with:
          args:
            "deploy --region atl --app ${{ env.SHA }}-dev-<myapp> -c
            fly.development.toml"
      - name: Post link to PR Preview
        uses: actions-ecosystem/action-create-comment@v1
        with:
          github_token: ${{ secrets.github_token }}
          body: |
            Your PR is deployed, see it [here](https://${{ env.SHA }}-dev-<myapp>.fly.dev)
            ...
            Delete the app [here](https://fly.io/apps/${{ env.SHA }}-dev-<myapp>/settings)
            Delete the db [here](https://fly.io/apps/${{ env.SHA }}-dev-<myapp>-db/settings)

```

This works pretty well if you only have phoenix + DB like me, just replace `<myapp>`, `<myorg>`, and the region with your info!

My strategy going forward for deleting them (currently I’m doing this manually) is to modify the above to add a pr number to the name, then create a third workflow to use `fly list apps -<prnumber>-dev` and then use `fly destroy` on those. I also haven’t needed to worry about multiple regions yet but I’ll cross that bridge when we come to it 🙂

I would also love any and all feedback on this! I’m loving the experience so far.

---

<div class="post-metadata">

**Author:** ![nahtnam](https://sea1.discourse-cdn.com/fly1/user_avatar/community.fly.io/nahtnam/32/13664_2.png) [@nahtnam](https://community.fly.io/u/nahtnam)\
**Post date:** [February 19, 2022, 1:28am UTC](https://community.fly.io/t/pull-request-previews-and-preview-environments/4120/8 "2022-02-19T01:28:35Z")

</div>

This looks awesome! I think theres a way to run an action when a PR is merged, maybe you can use that to delete it? And yeah definitely think using the PR # is the “standard” practice so its much easier to find and delete

I feel like this creates a lot of spam in the UI (you might see tens of apps), and again could be improved if fly supports this with some sort of namespace or deploy environment. I think vercel is king when it comes to easy of use

---

<div class="post-metadata">

**Author:** ![jsierles](https://sea1.discourse-cdn.com/fly1/user_avatar/community.fly.io/jsierles/32/595_2.png) [@jsierles](https://community.fly.io/u/jsierles)\
**Post date:** [February 20, 2022, 10:41am UTC](https://community.fly.io/t/pull-request-previews-and-preview-environments/4120/9 "2022-02-20T10:41:32Z")

</div>

Someone also built this: [GitHub Action for deplying staging apps on fly.io · Actions · GitHub Marketplace · GitHub](https://github.com/marketplace/actions/github-action-for-deplying-staging-apps-on-fly-io)

---

<div class="post-metadata">

**Author:** ![jimmyfm](https://avatars.discourse-cdn.com/v4/letter/j/919ad9/32.png) [@jimmyfm](https://community.fly.io/u/jimmyfm)\
**Post date:** [May 15, 2022, 7:07pm UTC](https://community.fly.io/t/pull-request-previews-and-preview-environments/4120/10 "2022-05-15T19:07:31Z")

</div>

Super appreciative of this thread, figured I would drop something here that seems to be working for my workflow (no pun intended) which is a Postgres DB + Nodejs & Prisma app (distroless container caused some quirks too so happy to expand on that for Prisma users).

**Background** :  
I only really wanted one DB per PR (hence the `if` statement on this job), and to then deploy the app on synchronize (subsequent code changes).

So more simply put, I have taken from the approaches described above, pinned these to the PR number, and split `db` and `app` into separate jobs.

Happy to explain more, but just wanted to put this out here as I just got it work 🎉

```auto
# branch.yml
name: Deploy Preview
on:
  pull_request

env:
  FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
  FLY_ORG: ${{ secrets.FLY_ORG }}
  FLY_PREVIEW_APP: ...

jobs:
  db:
    if: github.event.action == 'opened'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Github repo
        uses: actions/checkout@v3
      - name: Get PR number
        uses: jwalton/gh-find-current-pr@v1
        id: findPr
        with:
          # Can be "open", "closed", or "all". Defaults to "open".
          state: open
      - name: Build DB
        uses: superfly/flyctl-actions@1.3
        with:
          args:
            'pg create --region ord --vm-size shared-cpu-1x --name pr-${{ steps.findPr.outputs.pr }}-dev-${{ env.FLY_PREVIEW_APP }}-db --volume-size 1 --organization ${{ env.FLY_ORG }}
            --initial-cluster-size 1 '
      - name: Create fly app
        uses: superfly/flyctl-actions@1.3
        with:
          args: 'apps create pr-${{ steps.findPr.outputs.pr }}-dev-${{ env.FLY_PREVIEW_APP }} --org ${{ env.FLY_ORG }}'
      - name: Connect app to DB
        uses: superfly/flyctl-actions@1.3
        env:
          FLY_APP: pr-${{ steps.findPr.outputs.pr }}-dev-${{ env.FLY_PREVIEW_APP }}
        with:
          args: 'pg attach --postgres-app pr-${{ steps.findPr.outputs.pr }}-dev-${{ env.FLY_PREVIEW_APP }}-db'
      - name: Set up secret_key_base with fly secrets
        uses: superfly/flyctl-actions@1.3
        env:
          FLY_APP: pr-${{ steps.findPr.outputs.pr }}-dev-${{ env.FLY_PREVIEW_APP }}
        with:
          args: 'secrets set ...'
      - name: Post link to PR Preview
        uses: actions-ecosystem/action-create-comment@v1
        with:
          github_token: ${{ secrets.github_token }}
          body: |
            Delete the db [here](https://fly.io/apps/pr-${{ steps.findPr.outputs.pr }}-dev-${{ env.FLY_PREVIEW_APP }}-db/settings)

  app:
    if: github.event.action == 'synchronize'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Github repo
        uses: actions/checkout@v3
      - name: Get PR number
        uses: jwalton/gh-find-current-pr@v1
        id: findPr
        with:
          # Can be "open", "closed", or "all". Defaults to "open".
          state: open
      - name: Modify fly.preview.toml file
        run: |
          sed -i 's/<REPLACED_IN_CI>/pr-${{ steps.findPr.outputs.pr }}/g' fly.preview.toml
      - name: Deploy app
        uses: superfly/flyctl-actions@1.3
        with:
          args:
            'deploy --region ord --app pr-${{ steps.findPr.outputs.pr }}-dev-${{ env.FLY_PREVIEW_APP }} -c
            fly.preview.toml'
      - name: Post link to PR Preview
        uses: actions-ecosystem/action-create-comment@v1
        with:
          github_token: ${{ secrets.github_token }}
          body: |
            Your PR is deployed, see it [here](https://pr-${{ steps.findPr.outputs.pr }}-dev-${{ env.FLY_PREVIEW_APP }}.fly.dev)
            ...
            Delete the app [here](https://fly.io/apps/pr-${{ steps.findPr.outputs.pr }}-dev-${{ env.FLY_PREVIEW_APP }}/settings)

```

---

<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 20, 2022, 12:17am UTC](https://community.fly.io/t/pull-request-previews-and-preview-environments/4120/11 "2022-05-20T00:17:47Z")

</div>

I’ve also set this up here: [Demo: Heroku-style Review Apps](https://community.fly.io/t/demo-heroku-style-review-apps/5160)

---

<div class="post-metadata">

**Author:** ![vinnie](https://sea1.discourse-cdn.com/fly1/user_avatar/community.fly.io/vinnie/32/12391_2.png) [@vinnie](https://community.fly.io/u/vinnie)\
**Post date:** [May 20, 2023, 8:14am UTC](https://community.fly.io/t/pull-request-previews-and-preview-environments/4120/12 "2023-05-20T08:14:02Z")

</div>

At [platform.sh](https://platform.sh/) we have had IaC and preview (development) environments for the past 12 years, with GitOps at the centre of the entire development workflow.

---

<div class="post-metadata">

**Author:** ![pmbanugo](https://sea1.discourse-cdn.com/fly1/user_avatar/community.fly.io/pmbanugo/32/24280_2.png) [@pmbanugo](https://community.fly.io/u/pmbanugo)\
**Post date:** [September 6, 2023, 3:11pm UTC](https://community.fly.io/t/pull-request-previews-and-preview-environments/4120/13 "2023-09-06T15:11:03Z")

</div>

I started building [FlyCD](https://flycd.dev/) some months ago to solve this problem. It provides preview apps for PRs and automatically attaches a preview database when desired. You can check out the demo [video](https://dub.sh/flycd-demo) to get a glimpse of how it works.

The documentation is still a work in progress, so some features are not mentioned in the docs.
