How to Deploy Preview Environments in Pull Requests with Github Actions and Bunnyshell

How to Deploy Preview Environments in Pull Requests with Github Actions and Bunnyshell

The previous guide provided instructions on how to set up an environment in Bunnyshell from a GitHub repository. In this guide, we will explain how to use GitHub Actions and Bunnyshell together to create dynamic preview environments for pull requests. This combination allows for testing changes in a live environment before merging them into the main branch, which can help detect issues early on in the development process.

What are Preview Environments?

Preview environments, also known as preview apps or dynamic staging environments, are temporary environments spun up for each pull request. They serve as a sandbox where developers can test their changes in a live, isolated environment, separate from the main production or development environments. This approach allows for isolated testing and interactive review of changes, aiding in the early detection of potential issues. While they're not exact replicas of the production environment, they're close enough to provide a reliable approximation of how the changes will behave in production.

Understanding the Bunnyshell Configuration file

The bunnyshell.yaml file is a configuration file that Bunnyshell uses to create environments. It specifies the details of the environment, such as the services it should include, the resources it should use, and any environment variables it needs. In the case of Docker-Compose based environments, each service in the bunnyshell.yaml file corresponds to one container in namespace where the environment runs, and the configuration options for each service correspond to the options you would specify in a docker-compose.yaml file.

Advanced option: If you're connecting your own Kubernetes cluster, you have the option to deploy Helm charts or Manifest based applications.

Understanding Github Actions Workflows

GitHub Actions is a robust CI/CD platform that automates software workflows within your GitHub repository. In the context of this guide, GitHub Actions plays a crucial role in creating dynamic preview environments for each pull request. This means that for every proposed change via a pull request, an automatic, live environment is spun up, enabling you to see your modifications in real-time.

Prerequisites

Steps

  1. Adding the bunnyshell.yaml to the Github repository
  2. Creating a workflow to PREPARE the environment configuration
  3. Creating a workflow to DEPLOY preview environments
  4. Creating a workflow to DELETE preview environments
  5. Optional: Creating a workflow to STOP preview environments
  6. Optional: Creating a workflow to START preview environments
  7. Enabling the Github Actions workflows

** All the workflows we will be using in this article can be found in Bunnyshell’s Demo-Books repository.

Adding the bunnyshell.yaml to the Github repository

1. Navigate to your environment page in the Bunnyshell UI, then click on the Configuration button.

2. Click on the copy to clipboard icon on the upper right side of the text editor to select and copy the YAML configuration.

3. Create a new branch locally in your project.

git checkout -b add-preview-environments

4. Create a new directory in your local project called .bunnyshell

5. Create a new file called bunnyshell.yaml and paste the YAML configuration previously copied from the Bunnyshell UI editor.

Defining Repository Variables and Secrets

For the following workflows to function correctly, it's crucial to define certain repository variables and actions. These variables and actions provide the necessary context and permissions for the workflows to interact with your GitHub repository and the Bunnyshell platform.

Why Define Repository Variables?

Repository variables are like environment variables at the repository level. They store data that your workflows can use. In the context of these workflows, repository variables are used to store information such as the Bunnyshell project ID, cluster ID, and the list of allowed users. These variables are then used within the workflows to perform actions like deploying an environment or deleting an environment.

How to Define Repository Variables?

To define repository variables in GitHub, you need to go to the Settings tab of your repository, then click on Secrets and Variables and choose Actions. Here, you can add new repository variables or secrets, which can act as encrypted environment variables. When you create a secret, GitHub encrypts the secret and uses it as an environment variable in your workflow run.

Let’s define the following VARIABLES to be used in the preview environments workflows:

1. Name: BUNNYSHELL_PROJECT_ID
Value: you can get the project ID by running bns projects list in your localhost Bunnyshell CLI as below.

preview-environments

preview-environments

2. Name: BUNNYSHELL_CLUSTER_ID
Value: you can get the Kubernetes cluster integration ID by running the following command in your CLI:

bns k8s list --organization <your_organization_id> --environment ''
preview-environments

preview-environments

3. Name: BUNNYSHELL_ALLOWED_USERS
Value: can be * for all users or a JSON list such as ["<github_user>"] where github_user is your Github username.

 

Next you’ll define the BUNNYSHELL_ACCESS_TOKEN Repository Secret who will allow Github Actions to authenticate in the Bunnyshell platform. It’s value can be taken from your Bunnyshell account at Bunnyshell.

Creating a workflow to PREPARE the environment configuration

Create a .github/workflows directory in your repository on GitHub or local project if this directory does not already exist.

In the .github/workflows directory, create a file named bunnyshell-previews-prepare.yml and copy-paste the following YAML snippet into the new file.

name: Bunnyshell - Prepare Preview Environment Configuration
on:
pull_request:
types: [ opened, reopened, synchronize ]
branches:
- '*'
paths-ignore:
- "*.md"
issue_comment:
types: [ created, edited ]
jobs:
deploy:
name: Deploy Environment
uses: bunnyshell/workflows/.github/workflows/prepare-configuration.yaml@v1
with:
bunnyshell-yaml-path: .bunnyshell/bunnyshell.yaml
allowed-users: ${{ vars.BUNNYSHELL_ALLOWED_USERS }}
git-ref: refs/pull/${{ github.event.number || github.event.issue.number }}/head

The workflow is triggered when a pull request is opened, reopened, or synchronized, or when an issue comment is created or edited. It ignores changes in markdown files (.md).

The workflow has a single job named Deploy Environment that uses the prepare-configuration.yaml workflow from the Bunnyshell workflows repository and is designed to prepare the configuration for a preview environment in Bunnyshell.

The job takes several inputs:

bunnyshell-yaml-path: The path to the bunnyshell.yaml file in your project repository.

allowed-users: The Github users for whom this workflow is ran when creating Pull Requests.

git-ref: The reference to the pull request or issue.

 

Please make sure the bunnyshell-yaml-path is correctly reflecting the directory of your environment configuration file.

Creating a workflow to DEPLOY preview environments

In the .github/workflows directory, create a file named bunnyshell-previews-deploy.yml, then copy-paste the following YAML snippets, one by one, into the new file.

Workflow Trigger

name: Bunnyshell - Deploy Preview Environment
on:
workflow_run:
workflows:
- "Bunnyshell - Prepare Preview Environment Configuration"
types:
- completed

The workflow is triggered when the workflow named Bunnyshell - Prepare Preview Environment Configuration is completed.

Permissions

permissions:
pull-requests: write

The workflow has write permissions for pull requests.

The workflow has two main jobs: Load artifact and Deploy Environment.

Load Artifact Job

The Load artifact job is responsible for downloading and loading the artifact data, setting some variables, and leaving a comment if the deployment is unapproved.

Job definition

jobs:
load-artifact:
name: Load artifact
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
outputs:
pr-number: ${{ env.PR_NUMBER }}
skip-deployment: ${{ fromJSON(env.FLAGS_JSON).skip_deployment }}
is-pull-request-event: ${{ fromJSON(env.EVENT_JSON).pull_request != '' }}
bunnyshell-yaml-contents: ${{ env.BUNNYSHELL_YAML_CONTENTS }}

The job has four outputs:

pr-number: The number of the pull request.

skip-deployment: A flag indicating whether to skip the deployment, that means leaving the environment in Draft mode after creating the Pull Request.

is-pull-request-event: A flag indicating whether the event is a pull request event.

bunnyshell-yaml-contents: The contents of the bunnyshell.yaml file that are passed when loading the artifact.

Download Artifact Step

    steps:
- name: Download artifact
uses: actions/github-script@v6
with:
script: |
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name == "bunnyshell"
})[0];
if (matchArtifact === undefined) {
throw TypeError('Build Artifact not found!');
}
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
let fs = require('fs');
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/artifact.zip`, Buffer.from(download.data));

This step uses the github-script action to download the artifact named bunnyshell from the Prepare Environment Configuration workflow run that triggered this workflow. The downloaded artifact is saved as a zip file in the workspace.

Load Artifact Step

      - name: Load artifact data
run: |
unzip artifact.zip

echo 'EVENT_JSON<<EOFEVENTJSON' >> $GITHUB_ENV
cat event.json >> $GITHUB_ENV
echo -e '\nEOFEVENTJSON' >> $GITHUB_ENV

echo 'FLAGS_JSON<<EOFFLAGSJSON' >> $GITHUB_ENV
cat flags.json >> $GITHUB_ENV
echo -e '\nEOFFLAGSJSON' >> $GITHUB_ENV

if [ -f "bunnyshell.yaml" ]; then
echo 'BUNNYSHELL_YAML_CONTENTS<<EOFBNSYAML' >> $GITHUB_ENV
cat bunnyshell.yaml >> $GITHUB_ENV
echo -e '\nEOFBNSYAML' >> $GITHUB_ENV
fi

This step unzips the downloaded artifact and loads the data from the event.json, flags.json, and bunnyshell.yaml files into environment variables.

Set Variables Step

      - name: Set variables
run: |
echo "IS_PULL_REQUEST=${{ fromJSON(env.EVENT_JSON).pull_request != '' }}" >> $GITHUB_ENV
echo "IS_ISSUE_COMMENT=${{ fromJSON(env.EVENT_JSON).issue.pull_request != '' }}" >> $GITHUB_ENV
echo "PR_NUMBER=${{ fromJSON(env.EVENT_JSON).issue.number || fromJSON(env.EVENT_JSON).number }}" >> $GITHUB_ENV

This step sets three environment variables based on the data loaded from the event.json file.

Comment Step

      - name: Comment unapproved deployment
uses: thollander/actions-comment-pull-request@v2
if: ${{ fromJSON(env.FLAGS_JSON).skip_deployment }}
with:
message: |
### Bunnyshell Preview Environment automatic creation skipped due to restricted files changes.

Add a comment containing `/bns:deploy` to approve the creation of the environment.
comment_tag: bunnyshell-preview-env
pr_number: ${{ env.PR_NUMBER }}

This step uses the actions-comment-pull-request action to leave a comment on the pull request if the deployment is unapproved.

Deploy Environment

Job Job definition

deploy:
name: Deploy Environment
needs: load-artifact
uses: bunnyshell/workflows/.github/workflows/deploy-env.yaml@v1
concurrency: bns-deploy-${{ needs.load-artifact.outputs.prNumber }}
if: ${{ github.event.workflow_run.conclusion == 'success' && needs.load-artifact.outputs.skip-deployment == 'false' }}
with:
pr-number: "${{ needs.load-artifact.outputs.pr-number }}"
project-id: ${{ vars.BUNNYSHELL_PROJECT_ID }}
cluster-id: ${{ vars.BUNNYSHELL_CLUSTER_ID }}
env-name: "PR #${{ needs.load-artifact.outputs.pr-number }}"
bunnyshell-yaml-contents: ${{ needs.load-artifact.outputs.bunnyshell-yaml-contents }}
comment-on-pr: true
deploy-as-stopped: ${{ needs.load-artifact.outputs.is-pull-request-event == 'true' }}
secrets:
bunnyshell-access-token: ${{ secrets.BUNNYSHELL_ACCESS_TOKEN }}

The Deploy Environment job, which depends on the Load artifact job, deploys the environment using the deploy-env.yaml workflow from the Bunnyshell workflows repository.

The job only runs if the conclusion of the workflow run that triggered it was successful and the skip-deployment output from the Load artifact job is false.

The job takes several inputs:

pr-number: The number of the pull request, which is an output from the Load artifact job.

project-id: The ID of the project in Bunnyshell, which is a repository variable.

cluster-id: The ID of the cluster in Bunnyshell, which is a repository variable.

env-name: The name of the environment to deploy, which is typically named 'DemoBooks PR #' followed by the number of the pull request.

bunnyshell-yaml-contents: The contents of the bunnyshell.yaml file, which is an output from the Load artifact job.

comment-on-pr: A boolean value indicating whether to comment on the pull request.

deploy-as-stopped: A boolean value indicating whether to deploy the environment as stopped. This is true if the event is a pull request event, which is determined by an output from the Load artifact job.

The job uses the BUNNYSHELL_ACCESS_TOKEN secret, which is an encrypted environment variable that stores the access token for Bunnyshell. You defined this variable as a repository secret in the first step of this tutorial.

Entity diagram that describes how Github Actions Workflows interact with Bunnyshell

Entity diagram that describes how Github Actions Workflows interact with Bunnyshell

Creating a workflow to DELETE preview environments

In the .github/workflows directory, create a file named bunnyshell-previews-destroy.yml and copy-paste the following YAML snippets into the new file.

name: Bunnyshell - Delete Preview Environment
concurrency: bns-delete-${{ github.event.number || github.event.issue.number }}
on:
pull_request_target:
types: [closed]
branches:
- '*'
issue_comment:
types: [ created, edited ]
permissions:
pull-requests: write
jobs:
start:
name: Delete Environment
uses: bunnyshell/workflows/.github/workflows/delete-env.yaml@v1
with:
project-id: ${{ vars.BUNNYSHELL_PROJECT_ID }}
env-name: 'PR #${{ github.event.number || github.event.issue.number }}'
allowed-users: ${{ vars.BUNNYSHELL_ALLOWED_USERS }}
comment-on-pr: true
secrets:
bunnyshell-access-token: ${{ secrets.BUNNYSHELL_ACCESS_TOKEN }}

This workflow is triggered when a pull request is closed or an issue comment is created or edited with the /bns:delete command. It has a single job named Delete Environment that uses the delete-env.yaml workflow from the Bunnyshell workflows repository.

The job automatically takes the following inputs:

project-id: The ID of the project in Bunnyshell.

env-name: The name of the environment to delete. It is typically named 'DemoBooks PR #' followed by the number of the pull request or issue.

allowed-users: The users who are allowed to perform this action.

comment-on-pr: A boolean value indicating whether to comment on the pull request.

bunnyshell-access-token: The access token for Bunnyshell.

Optional: Creating a workflow to STOP preview environments

The STOP action is very useful when the environment is not needed right away after the Pull Request has been created and can be called to reduce the resources consumed while it would’ve been idle.

To add this workflow, create a file named bunnyshell-previews-stop.yml in the .github/workflows directory and copy-paste the following code snippet.

name: Bunnyshell - Stop Preview Environment
concurrency: bns-start-stop-${{ github.event.issue.number }}
on:
issue_comment:
types: [created, edited]
permissions:
pull-requests: write
jobs:
start:
name: Stop Environment
uses: bunnyshell/workflows/.github/workflows/stop-env.yaml@v1
with:
project-id: ${{ vars.BUNNYSHELL_PROJECT_ID }}
env-name: 'PR #${{ github.event.issue.number }}'
allowed-users: ${{ vars.BUNNYSHELL_ALLOWED_USERS }}
comment-on-pr: true
secrets:
bunnyshell-access-token: ${{ secrets.BUNNYSHELL_ACCESS_TOKEN }}

This workflow is triggered when an issue comment is created or edited with the /bns:stop command. It has a single job named Stop Environment that uses the stop-env.yaml workflow from the Bunnyshell workflows repository. Technically speaking, the job calls the Stop action from the Bunnyshell platform and reduces the Deployment replica set to 0 for the pull-request environment.

When called, it automatically takes several inputs:

project-id: The ID of the project in Bunnyshell.

env-name: The name of the environment to stop. It is typically named 'PR #' followed by the number of the issue.

allowed-users: The users who are allowed to perform this action.

comment-on-pr: A boolean value indicating whether to comment on the pull request.

bunnyshell-access-token: The access token for Bunnyshell.

Optional: Creating a workflow to START preview environments

The START action works on an environment that was previously stopped. If the environment has never been deployed, the START action will trigger the DEPLOY firstly.

To add this workflow, create a file named bunnyshell-previews-start.yml in the .github/workflows directory and copy-paste the following code snippet.

name: Bunnyshell - Start Preview Environment
concurrency: bns-start-stop-${{ github.event.issue.number }}
on:
issue_comment:
types: [created, edited]
permissions:
pull-requests: write
jobs:
start:
name: Start Environment
uses: bunnyshell/workflows/.github/workflows/start-env.yaml@v1
with:
project-id: ${{ vars.BUNNYSHELL_PROJECT_ID }}
env-name: 'PR #${{ github.event.issue.number }}'
allowed-users: ${{ vars.BUNNYSHELL_ALLOWED_USERS }}
comment-on-pr: true
secrets:
bunnyshell-access-token: ${{ secrets.BUNNYSHELL_ACCESS_TOKEN }}

This workflow is triggered when an issue comment is created or edited. It has a single job named Start Environment that uses the start-env.yaml workflow from the Bunnyshell workflows repository. The job takes several inputs:

project-id: The ID of the project in Bunnyshell.

env-name: The name of the environment to start. It is typically named 'DemoBooks PR #' followed by the number of the issue.

allowed-users: The users who are allowed to perform this action.

comment-on-pr: A boolean value indicating whether to comment on the pull request.

bunnyshell-access-token: The access token for Bunnyshell.

Enabling the Github Actions workflows

While some workflows might get triggered from any branch, we need to push these workflows to the master or main branch to have all the necessary permissions to run.

1. Firstly, let’s add all the changes to the staging area and create a commit. Remember to isolate any unrelated work in progress before committing the changes.

git status
git add .github/workflows/
git commit -m "Add actions workflow to deploy preview environments in pull requests"

2. Push the local branch and create a pull request. Review, then merge the pull request so that the workflows we created earlier can be reflected in the main branch and run when new PRs are created.

git push origin add-preview-environments

Testing the workflows

Now that we have our GitHub Actions workflow set up on the main branch, it's time to test it out. Here's a general guide on how you can do this:

Create a new branch: After making your changes, create a new branch in your repository. This branch will contain your changes and will be used to create the pull request.

Make some changes: Start by making some changes to your code. This could be anything - a new feature, a bug fix, or even a simple comment. The goal here is to create a change that we can submit as a pull request.

Commit your changes: Commit your changes to this new branch. Remember to write a clear and concise commit message that describes what changes you've made.

Push your branch: Push your branch to your GitHub repository. This will make your branch and its changes available online on GitHub.

Create a pull request: Finally, go to your GitHub repository and create a new pull request. Select your branch in the "compare" dropdown, and follow the prompts to create the pull request.

 

Now your new prepare config workflow should start and you can view the progress and results of the workflow in the "Actions" tab of your GitHub repository.

Github Actions dashboard with the workflows deployment logs.

Github Actions dashboard with the workflows deployment logs.

If your deployment workflow has successfully ran, you should see a comment posted by the github-actions bot on your new Pull Request.

Github Actions commented with the live preview environment details.

Github Actions commented with the live preview environment details.

Optional: Now that the deployment is successful, let’s see if the STOP and START actions work as expected. To test them, create a new comment on your Pull Request and write the /bns:stop command. Your stop workflow should be triggered and in couple of seconds to a minute, your environment should be stopped in Bunnyshell.

The Demo-Books PR environment stopping in the Bunnyshell dashboard.

The Demo-Books PR environment stopping in the Bunnyshell dashboard.

Optional: If the stopping was successful, repeat the same step, this time with the /bns:start command as a comment on your PR.

The Demo-Books PR environment starting in the Bunnyshell dashboard.

The Demo-Books PR environment starting in the Bunnyshell dashboard.

Finally, when you merge or close your Pull Request, the DELETE workflow should be called and your Bunnyshell environment should be deleted.

Congratulations! You finished implementing production-grade ephemeral environments in your CI pipeline 🚀.

Troubleshooting common issues

Here are a few troubleshooting tips:

Workflow doesn't trigger: If your workflow isn't triggering when you expect it to, check the on section of your workflow file. Make sure you've specified the correct events and branches.

Workflow fails: If your workflow fails, check the logs in the "Actions" tab of your GitHub repository. The logs will show you which step failed and provide an error message that can help you troubleshoot the issue.

Environment doesn't start in Bunnyshell: If your environment doesn't start in Bunnyshell, check the bunnyshell.yaml file. Make sure all the services and their configurations are correct. Also, check the logs in Bunnyshell for any error messages.

Secrets and Variables aren't available in workflow: If your secrets aren't available in your workflow, make sure you've added them to the "Secrets" section of your repository settings. Also, remember that secrets are not passed to workflows that are triggered by pull requests from forks.

Workflow takes a long time to run: If your workflow takes a long time to run, consider optimizing your bunnyshell.yml and/or Dockerfiles.

Remember, these are just general troubleshooting tips. The exact solution might vary depending on your specific setup and issue. Also consider reaching out to the Bunnyshell support team for further assistance.

Advanced use cases

While we've covered the basics of setting up preview environments with GitHub Actions and Bunnyshell, there are many advanced use cases that can further enhance your development workflow. Here are a few examples:

Selecting Pull Requests Based on Tags: GitHub Actions allows you to trigger workflows based on various events, including the tagging of pull requests. This can be useful if you want to create preview environments only for certain pull requests. For example, you could set up your workflow to only run when a pull request is tagged with preview.

Running Automated Tests: One of the major benefits of preview environments is the ability to test your changes in a live, production-like environment. You can take this a step further by setting up your GitHub Actions workflow to automatically run tests in the preview environment. This can help catch issues early in the development process.

Building in the CI and Pushing to an Image Registry: If you're building the container images in the CI, you can set up your workflow to push them to an image registry where Bunnyshell can pull them from and deploy in the Kubernetes cluster.

Advanced Data Strategies: With platforms like Neon and PlanetScale, you can implement advanced data strategies in your preview environments. For example, their data branching model allows you to create isolated copies of your database in the cloud, providing a safe and efficient way to test your changes with realistic data. This allows you to test your changes with realistic data, without exposing sensitive information or affecting your production database.

Conclusions

We've covered a lot of ground in this guide, from understanding the basics of preview environments, GitHub Actions workflows, and the Bunnyshell configuration file, to exploring advanced use cases and best practices. Armed with this knowledge, now you can implement dynamic preview environments in your own projects. If you’d like to learn more about Bunnyshell read our comprehensive docs and if you haven't done so already, consider registering for a free account.