Hooking up a Middleman project to deploy a static site to Heroku with Bitrise

This blogpost will guide you through connecting a Middleman project to Bitrise, triggering a Middleman build and deploying the generated static site to Heroku on every git push to the repository of the Middleman project. This can be achieved with either Bitrise CLI or on bitrise.io

This blogpost will guide you through connecting a Middleman project to Bitrise, triggering a Middleman build and deploying the generated static site to Heroku on every git push to the repository of the Middleman project. This can be achieved with either Bitrise CLI or on bitrise.io.

For this, you will need the following:

  • a git repository with a Middleman project (can be hosted anywhere)
  • a Heroku account - you will create a Heroku App which will host the static site. Optionally you can create another Heroku App which will host the static site in staging mode.

By the end, you will be able to:

  • deploy a generated static site to your Heroku staging git repository by every git push to your Middleman project's git repository's develop branch
  • deploy a generated static site to your Heroku production git repository by every git push to your Middleman project's git repository's master branch

1. Create & configure a Heroku repository

On Heroku, create a new App. For this tutorial, I will use bitrise-test-heroku-production.

Go to the Settings tab and select Add buildpack. Add a buildpack with the following URL: https://github.com/heroku/heroku-buildpack-static. This is a cool buildpack for static sites, it only requires a static.json configuration file in the root directory of the repository, and you can store your static site in a directory named public_html, also located in the root directory.

Clone this repository, then have your first commit by adding a static.json file without any configuration: {}.

You can repeat the same process creating another Heroku App for staging. For this tutorial, I will use bitrise-test-heroku-staging.

2. Create a Bitrise App

Sign in (or sign up if you haven't already) to Bitrise, then on the Dashboard, select Add new App.

On the Add new App page, specify the git repository of your Middleman project by either selecting it from one of the lists (you will need to connect your GitHub or Bitbucket account if you would like Bitrise to be able to list your repositories - you can do this on the Account Settings page), or by specifying the git clone URL for it. For this tutorial, I will use bitrise-test-middleman-project, selected from my connected provider's repository list.

In the Setup repository access section, auto-add the SSH keypair generated by Bitrise, by selecting Auto-add this key.

In the Validation setup section, because we won't need Bitrise to scan for valid project configurations, select Configure manually.

In the Project Build Configuration section, select Android for project type, since we are going to need Ubuntu set up on the worker machine which will run our Builds and Bitrise assigns worker machines with Ubuntu to Android projects. Enter master for branch name, enter . for gradle file, gradle task and gradlew path.

In the Webhook setup section, select Register a Webhook for me. The webhook registration is needed so that everytime you push to this repository, your repository host will trigger a Build on Bitrise.

Your App is created, yay! Select it from the Dashboard to view its details.

3. Configure Workflow pipeline & triggers

Select the Workflow tab, then select Manage Workflows. This is where we will configure the Workflows which will be triggered by our git host on every git push. The process will consist of the followings:

  • clone the repository of the Middleman project (if ran on bitrise.io, otherwise - in CLI mode - the project is already given)
  • clone the Heroku repository, store it in a temporary directory
  • build Middleman project
  • clear the previous static page from the Heroku repository, move the new build to this location
  • git push the changes to the Heroku repository

Remove all the steps from the primary Workflow, then rename it to deploy. This will be the triggered Workflow by every git push... sort of. The beginning _ character means this is a utility Workflow, meaning it will not be triggered directly. Create two another Workflows, deploy-production and deploy-staging. These will be the actual Workflows triggered by the git pushes. They will trigger our utility Workflow afterwards. For this, you need to do the following for both Workflows: on the bottom of the Workflow's page, where it says *No other Workflows will run after this Workflow*, select *Add* and select the deploy Workflow, then save.

Select Triggers in the side menu. Remove all the triggers, then add one. The pattern should be master, the Workflow should be deploy-production, the pull request trigger checkbox should be disabled - by this, every git push (not pull requests) on the master branch will trigger the deploy-production Workflow. Add another one with the pattern develop and with the Workflow deploy-staging, pull request trigger checkbox disabled, then save.

4. Configure Workflows

Now that we've got our Bitrise App trigger-configured, time to configure the actual work we would like Bitrise to do for us. For this, select the _deploy Workflow. Add a Step called Script. In this step, you can write your own Bash script. Rename the Step to Prepare Temporary Directories. Paste the following code snippet to the script content:


#!/bin/bash
set -ex

tempdir="$(mktemp -d)"

temp_orig_netrc_path="$tempdir/orig.netrc"
temp_heroku_path="$tempdir/heroku"

envman add --key TEMP_ORIG_NETRC_PATH --value "$temp_orig_netrc_path"
envman add --key TEMP_HEROKU_PATH --value "$temp_heroku_path"
Copy code

This script will create temporary directories for the copy of our netrc file and for our Heroku App git target path. It will also use envman to save these paths as Environment Variables, so later we will be able to access them in other Steps.

Add another Step called Git Clone Repository, then rename it to Git Clone Middleman Project Repository. We don't need any other configuration for this Step.

Add another Script Step, then rename it to Prepare Heroku Git Clone. The script content should be the following:


#!/bin/bash
set -ex

touch ~/.netrc
cp ~/.netrc "$TEMP_ORIG_NETRC_PATH"

> ~/.netrc
chmod 0600 ~/.netrc
echo "machine api.heroku.com" >> ~/.netrc
echo "  login [email protected]" >> ~/.netrc
echo "  password ${HEROKU_API_TOKEN}" >> ~/.netrc
echo "machine git.heroku.com" >> ~/.netrc
echo "  login [email protected]" >> ~/.netrc
echo "  password ${HEROKU_API_TOKEN}" >> ~/.netrc
Copy code

This script will copy the original netrc file to our previously created temporary directory, then set it up with required data for the upcoming Heroku git clone. This consists of an arbitrary email, and our Heroku API token. The latter is a yet undefined Environment Variable - we need to add this. You can get your Heroku API token in the Manage account menu on the Heroku website. Since this is private data, you should add it as a secret Environment Variable in the Secret Env Vars menu in the side menu. The key should be HEROKU_API_TOKEN, the value should be the API token.

Add another Git Clone Repository Step, then rename it to Git Clone Heroku Repository. By default, this step tries to clone with the initially set values (URL, commit hash, tag, branch, etc.) which is not what we want - they point to our Middleman project. The repository URL should be https://git.heroku.com/$HEROKU_APP_ID.git - this is the generic form of the Heroku git urls. Notice that this URL needs a yet undefined Environment Variable, the $HEROKU_APP_ID. We will deal with this later. The commit and the tag fields should be empty, the branch should be master, the pull request ID should be empty, the target directory should be our temporary directory $TEMP_HEROKU_PATH, the SSH private key should be empty since we are using the public URL.

Add another Script Step, then rename it to Build Middleman Project. The script content should be the following:


#!/bin/bash
set -ex

if ! gem list bundler -i ; then
    gem install bundler
fi

bundle install
sudo apt-get -y install nodejs

rm -rf build
bundle exec middleman build --verbose --no-clean

rm -rf $TEMP_HEROKU_PATH/public_html
mv build $TEMP_HEROKU_PATH/public_html
Copy code

This script will install bundler and Node.js (if not found), runs a Middleman build, moves the generated static site to the public_html directory of the Heroku temporary directory.

Add another Step called Change Working Directory for subsequent Steps - for the rest of the Steps we will be using the Heroku temporary directory as working directory, so set it to $TEMP_HEROKU_PATH.

Add another Script step and rename it to Prepare Heroku Deploy. The script content should be the following:


#!/bin/bash
set -ex

git config user.email "[email protected]"
git config user.name "[email protected]"

git add -A

commit_message="$BITRISE_GIT_MESSAGE"
if [ -z "$commit_message" ] ; then
  commit_message="new release"
fi

git commit -m "$commit_message"
Copy code

This script configures the git with arbitrary user data, stages & adds all diffs in the Heroku repository, then commits it with the commit message of our Middleman project commit.

Add another Step called Heroku Deploy. No need for additional configuration, it uses our previously set Heroku API token, and our soon-to-be-set Heroku APP ID by default. This step deploys the working directory to the specified App's master branch. This is exactly what we need - remember: the working directory has been set to the Heroku temporary directory.

Add the last step, another Script step, renamed to Cleanup, with the following content:


#!/bin/bash
set -ex

if [ ! -z "$TEMP_HEROKU_PATH" ] ; then
    rm -rf $TEMP_HEROKU_PATH
fi

if [ ! -z "$TEMP_NETRC_PATH" ] ; then
  cp "$TEMP_NETRC_PATH" ~/.netrc
  rm -rf $TEMP_NETRC_PATH
fi
Copy code

This script will remove our Heroku temporary directory, restore our original netrc file, and remove the temporary netrc directory. This step should run even if any of the previous steps have failed, so turn on Should this step run even if a previous step failed.

We're almost ready. The only thing left is setting the $HEROKU_APP_ID Environment Variable. Since we're using two different Heroku Apps for production and staging, we should have this Environment Variable set in the deploy-production and in the deploy-staging differently. The deploy Workflow will run after both, so depending on the triggered Workflow, the Environment Variable used in the deploy Workflow will either be the one set in deploy-production or the one set in deploy-staging. For the former, it should be the name of the Heroku App used for production (in my case, bitrise-test-heroku-production), for the latter, it should be the one for staging (in my case, bitrise-test-heroku-staging). You can set Environment Variables for a specific Workflow by selecting the Workflow and at the top, Manage env. vars.

Remove the $BITRISE_PROJECT_PATH, $GRADLE_TASK and $GRADLEW_PATH App Environment Variables: we don't need them, and it makes our bitrise.yml shorter. Speaking of which, we still have one last thing to do, which only can be done in console mode, so select bitrise.yml in the side menu. If you would like to run this configuration in CLI mode, the Git Clone Heroku Repository and the Change Working Directory for subsequent Steps Steps would be skipped by default, since they are rarely needed in CLI mode. But in our case, both should run in either mode. For this, add run_if: '' for both steps in the YML file - this tells Bitrise to run the Step regardless of the environment it is ran in (by default, its value is ".IsCI" for these Steps).

5. Try it out!

We are ready. To test it, push a commit to your Middleman project's git repository's master branch - its generated build should be deployed to the Heroku production App. Push another commit to your Middleman project's git repository's develop branch - its generated build should be deployed to the Heroku staging App. Note: if you trigger a Build on Bitrise which generates a static site with no difference from the previous version on Heroku, the Build will fail, since there is no diff to commit to the Heroku repository. This can occur if you trigger a Build manually, or when changes in your Middleman project does not affect the generated static site.

6. Try it out in CLI mode!

You can also test this configuration in CLI mode. For this, you need to have Bitrise CLI installed. You can find more information about this here.

After installed, navigate to your Middleman project's directory. Download the bitrise.yml and paste in in this directory. Then, create a file named .bitrise.secrets.yml, with the following content:


envs:
- HEROKU_API_TOKEN: [your Heroku API token]
Copy code

Finally, run bitrise run deploy-staging if you want to deploy the current state to the Heroku staging App, or bitrise run deploy-production if you would like to deploy the current state to the Heroku production App.

bitrise.yml


---
format_version: 1.1.0
default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git
trigger_map:
- pattern: master
  is_pull_request_allowed: false
  workflow: deploy-production
- pattern: develop
  is_pull_request_allowed: false
  workflow: deploy-staging
workflows:
  _deploy:
    steps:
    - [email protected]:
        title: Prepare Temporary Directories
        inputs:
        - content: |-
            #!/bin/bash
            set -ex

            tempdir="$(mktemp -d)"

            temp_orig_netrc_path="$tempdir/orig.netrc"
            temp_heroku_path="$tempdir/heroku"

            envman add --key TEMP_ORIG_NETRC_PATH --value "$temp_orig_netrc_path"
            envman add --key TEMP_HEROKU_PATH --value "$temp_heroku_path"
    - [email protected]:
        title: Git Clone Middleman Project Repository
    - [email protected]:
        title: Prepare Heroku Git Clone
        inputs:
        - content: |-
            #!/bin/bash
            set -ex

            touch ~/.netrc
            cp ~/.netrc "$TEMP_ORIG_NETRC_PATH"

            > ~/.netrc
            chmod 0600 ~/.netrc
            echo "machine api.heroku.com" >> ~/.netrc
            echo "  login [email protected]" >> ~/.netrc
            echo "  password ${HEROKU_API_TOKEN}" >> ~/.netrc
            echo "machine git.heroku.com" >> ~/.netrc
            echo "  login [email protected]" >> ~/.netrc
            echo "  password ${HEROKU_API_TOKEN}" >> ~/.netrc
    - [email protected]:
        title: Git Clone Heroku Repository
        run_if: ''
        inputs:
        - repository_url: https://git.heroku.com/$HEROKU_APP_ID.git
        - commit: ''
        - tag: ''
        - branch: master
        - pull_request_id: ''
        - clone_into_dir: "$TEMP_HEROKU_PATH"
        - auth_ssh_private_key: ''
    - [email protected]:
        title: Build Middleman Project
        inputs:
        - content: |-
            #!/bin/bash
            set -ex

            if ! gem list bundler -i ; then
                gem install bundler
            fi

            bundle install
            sudo apt-get -y install nodejs

            rm -rf build
            bundle exec middleman build --verbose --no-clean

            rm -rf $TEMP_HEROKU_PATH/public_html
            mv build $TEMP_HEROKU_PATH/public_html
    - [email protected]:
        run_if: ''
        inputs:
        - path: "$TEMP_HEROKU_PATH"
    - [email protected]:
        title: Prepare Heroku Deploy
        inputs:
        - content: |-
            #!/bin/bash
            set -ex

            git config user.email "[email protected]"
            git config user.name "[email protected]"

            git add -A

            commit_message="$BITRISE_GIT_MESSAGE"
            if [ -z "$commit_message" ] ; then
              commit_message="new release"
            fi

            git commit -m "$commit_message"
    - [email protected]: {}
    - [email protected]:
        title: Cleanup
        is_always_run: true
        inputs:
        - content: |-
            #!/bin/bash
            set -ex

            if [ ! -z "$TEMP_HEROKU_PATH" ] ; then
                rm -rf $TEMP_HEROKU_PATH
            fi

            if [ ! -z "$TEMP_NETRC_PATH" ] ; then
              cp "$TEMP_NETRC_PATH" ~/.netrc
              rm -rf $TEMP_NETRC_PATH
            fi
    before_run: 
    after_run: 
  deploy-production:
    steps: 
    before_run: 
    after_run:
    - _deploy
    envs:
    - opts:
        is_expand: true
      HEROKU_APP_ID: bitrise-test-heroku-production
  deploy-staging:
    steps: 
    before_run: 
    after_run:
    - _deploy
    envs:
    - opts:
        is_expand: true
      HEROKU_APP_ID: bitrise-test-heroku-staging
Copy code
No items found.
The Mobile DevOps Newsletter

Explore more topics

App Development

Learn how to optimize your mobile app deployment processes for iOS, Android, Flutter, ReactNative, and more

Bitrise & Community

Check out the latest from Bitrise and the community. Learn about the upcoming mobile events, employee spotlights, women in tech, and more

Mobile App Releases

Learn how to release faster, better apps on the App Store, Google Play Store, Huawei AppGallery, and other app stores

Mobile DevOps

Learn Mobile DevOps best practices such as DevOps for iOS, Android, and industry-specific DevOps tips for mobile engineers

Mobile Testing & Security

Learn how to optimize mobile testing and security — from automated security checks to robust mobile testing and more.

Product Updates

Check out the latest product updates from Bitrise — Build Insights updates, product news, and more.

The Mobile DevOps Newsletter

Join 1000s of your peers. Sign up to receive Mobile DevOps tips, news, and best practice guides once every two weeks.