This post discusses how to configure GitHub to automatically build and host your app’s front-end. It’s targeted at people new to continuous deployment and GitHub Actions.


Continuous Integration (CI) and Continuous Deployment (CD) are software development practices that promote releasing your software more frequently in smaller increments. That lets you tighten the feedback loop and iterate faster.

Using GitHub Actions, you can configure a CI/CD pipeline to run when you push changes to your GitHub repository. In this post, we will introduce and explain a basic pipeline which creates a production build of your app and provides it to GitHub Pages for hosting. It’s easy to do, and a great introduction to automated deployment.

Why would I want a CI/CD pipeline?

At its core, a working CI/CD pipeline means that you will have a live version of your site which is automatically kept up to date with the source code. That means that you never have to manually deploy the app, improving productivity and meaning easier onboarding. However, that’s not the main benefit. It also means you get the benefits associated with tighter feedback loops and faster iterations:

  • If you break the build and your app no longer compiles, you will get an email from GitHub warning you
  • The deployment is always kept up-to-date, so people get the latest features immediately
  • You get user feedback faster, so less effort is wasted on features that your users hate
  • There is no single stressful ‘release’ event

By using CI/CD we can tighten the feedback loops and get features to users faster

How does it work?

We will use GitHub Actions to configure the CI/CD pipeline. Other providers are available, such as CircleCI, TravisCI, and Jenkins. They all share a lot of the same concepts, so it’s simple to transfer your learning from one to the other. We will use GitHub Actions for its strong integration with the main GitHub platform and its generous free tier:

  • Unlimited usage on public repositories
  • 2000 minutes pipeline runtime/month on private repositories

To configure a GitHub Actions pipeline, we need to write a workflow. A workflow is essentially a set of steps to run whenever some trigger event occurs. They are written as a .yml file, and in this post we will be discussing the script I wrote, available in this gist. It is triggered when a commit is pushed to the repository, building the app and pushing it to the gh-pages branch to be deployed.

Below, I have created an interactive walkthrough of the script. At each stage, it shows you the relevant code, the state of the git repository, and explains in words what is happening.

The workflow script requires a small amount of customisation on a per-app basis. The interactive walkthrough uses the version I configured for MuseTree, a Svelte app. However, the explanation focusses on general concepts that will be applicable to any app.

Click the Next and Prev buttons to progress through the visualisation.

← Prev ← Prev 1. Initial Repository State 2. Trigger the Workflow 3. Download the Code 4. Build the App 5. Create an Orphan Branch 6. Clear the Staging Area 7. Stage the Build Files 8. Move to the Root 9. Commit the Files 10. Push the Updated Build Next → Next →
name: AutomaticHost
on:
  push:
    branches: 
      - develop
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - uses: actions/setup-node@v1
      with:
        node-version: '10.x'
    - run: |
        npm ci
        npm run build
    - run: |
        git config --local user.email "action@github.com"
        git config --local user.name "GitHub Action"
        git checkout --orphan gh-pages
        git reset
        git add public/* -f
        git mv public/* ./ -k
        git commit -m "Update hosted version"
    - uses: ad-m/github-push-action@master
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        force: true
        branch: gh-pages

1 2 3 4 5 6 develop HEAD 1 2 3 4 5 6 origin/develop 6 origin/gh-pages Directory Staging HEAD 6 gh-pages HEAD 5 origin/develop src public index.html src public index.html index.html src

Initially, the remote repository has 2 branches. gh-pages has one commit containing the latest production build, which is what gets deployed by GitHub Pages (the gh is short for GitHub). develop contains the source code, which currently consists of 5 commits.

The workflow is triggered by someone pushing to develop. You can see that it now has 6 commits. However, the gh-pages branch is still using a production build of commit 5. The workflow's job is to update gh-pages.

The computer that runs your workflow is completely separate, and has to explicitly download the code from the repository. We use the built-in checkout action to get a local copy of the develop branch.

Here, we use npm to create a production build of our app. In my example, I am building a Svelte app, meaning I run npm run build. If you use this workflow, you should edit this part to fit whatever web framework you are using. Additionally, you should run any unit tests here and abort the pipeline if they fail.

We checkout a local branch named gh-pages with the --orphan option which means it has no commits. While it has the same name as its counterpart on the remote repository, it's completely separate and will eventually overwrite the remote gh-pages. Creating the new branch automatically stages everything from the develop branch.

We don't want to commit the contents of develop to gh-pages because it's not used and only makes our repository bigger. To empty the staging area, we need to call git reset.

Now, we stage the build output directory to be committed to gh-pages. In Svelte, that is public, but it depends on your web framework.

For GitHub Pages to host a site, we need index.html at the root of the repository. We can ask git to move the files, keeping them staged in their new location.

We commit the staged files to gh-pages. This is the first and only commit on the local gh-pages branch.

We do a force push to the remote gh-pages, overwriting its one commit with our one commit. GitHub Actions automatically provides the GitHub token, authenticating the push. That push triggers GitHub Pages to start hosting the new build.

How do I get started?

First, create a new GitHub Action. In your repository, click Actions, then New. Choose Set up a workflow yourself, and copy the script from this gist.

Edit the script to fit your app:

  • Change {BRANCH_NAME} to the branch you want to host - probably master or develop
  • Change {BUILD_COMMAND} to the relevant npm command that builds your front end - e.g. npm run build for a React app
  • Change {OUT_DIR} to the name of the build output directory - e.g. build for a React app

Commit the action to your repository, then enable GitHub Pages integration. From the main repository page, click Settings then scroll down to the GitHub Pages section. Set Source to gh-pages. Your app should soon be available at https://{GITHUB_USERNAME}.github.io/{REPO_NAME}.

Conclusion

GitHub is surprisingly versatile as an all-in-one platform for building and hosting your web app. Its generous free tier and strong integration with GitHub Actions make it an excellent way to start learning about continuous deployment.

Give it a try yourself, and have fun customising it to fit you and your project.