Heads up
I wrote another article two years ago about building with Travis CI, but from my experience in the past half year, GitHub Actions is, in all aspects, a better option than Travis CI.
You should also read that article if you’re unfamiliar with Jekyll, as I won’t be repeating common basics. This article will focus on GitHub Actions rather than building a Jekyll site in general.
Earlier this year, I switched my GitHub Pages build from CircleCI to GitHub Actions.
Yep, an article is missing for CircleCI, but why is it still needed? GitHub Actions is better than CircleCI in almost every aspect, except for its CPU that runs slightly slower than that of CircleCI.
1. Review
In my previous article on building with Travis CI, we went through the steps of setting up a local build environment for our Jekyll site. We set up a Ruby development environment, installed gem
and bundle
, wrote a Gemfile
, and built the Jekyll site locally.
If you’re not yet ready for this part, check out that article first. I’m going straight to the main content this time.
2. Setting up GitHub Actions
Getting GitHub Actions ready for building is much easier than Travis CI, as everything you need to do is to push a config file into .github/workflows
directory of your repository.
If you’re working on a forked repository, you may want to navigate to the “Actions” tab in your repository, and enable Actions there. Actions is disabled for forked repositories by default.
Configure build settings
You can use any name for the config file, but here I’ll go with build.yml
. Here’s a minimal set of steps you’ll need.
name: build
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-ruby@v1
with:
ruby-version: 2.7
- name: Setup cache for Bundler
id: cache
uses: actions/cache@v2
with:
path: vendor/bundle
key: ${{ runner.os }}-bundler-${{ hashFiles('Gemfile') }}
restore-keys: |
${{ runner.os }}-bundler-
- name: Install dependencies
run: |
bundle install --path=vendor/bundle
- name: Build site
run: bundle exec jekyll build --profile --trace
env:
JEKYLL_ENV: production
JEKYLL_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Unlike Travis CI, all GitHub Actions builds run in an identical environment, while specific languages and software are loaded at runtime. This workflow contains 5 steps, with each step being:
- Clone and checkout the repository. Contrary to Travis CI, GitHub Actions does not clone the repository automatically, as GitHub Actions is intended for more general purposes than only running Continuous Integrations.
- Setup Ruby development environment. This one is obvious, since Jekyll is written in Ruby.
- Setup cache. For the same reason as with Travis CI: Caching installed gems speeds up subsequent builds.
- Install dependencies. Self-explanatory.
- Build site. Same as above, self-explanatory.
The build process is mostly the same as on Travis CI, except that many steps that are automatically taken on Travis CI have to be written explicitly.
3. Deploy to GitHub
Access token
You’ve probably noticed that there’s a ${{ secrets.GITHUB_TOKEN }}
in the above GitHub Actions config. That’s a neat feature GitHub provides. The main downside is that the token has access only to the repository the workflow is running on (as well as any other public resources). So if you want to push to a different repository, you’ll still have to resort to creating your personal access token (PAT) for it.
To keep things simple, I’ll assume you’re pushing to the same repository for deployment, where the GitHub-provided token can be used.
Setting up deployment
The deploy script from the other Travis CI article is as follows (with names replaced, of course):
cd _site
git init
git config user.name "GitHub"
git config user.email "[email protected]"
git add --all
git commit --message "Auto deploy from GitHub Actions build $GITHUB_RUN_NUMBER"
git remote add deploy https://${{ secrets.GITHUB_TOKEN }}@github.com/<yourname>/<yourname>.github.io.git >/dev/null 2>&1
git push --force deploy master >/dev/null 2>&1
Again, replace <yourname>
with your GitHub username in the above script.
Now, instead of writing it to a file, we can add this script directly to the build config, as shown below:
- name: Deploy site
run: |
cd _site
git init
git config user.name "GitHub"
git config user.email "[email protected]"
git add --all
git commit --message "Auto deploy from GitHub Actions build $GITHUB_RUN_NUMBER"
git remote add deploy https://${{ secrets.GITHUB_TOKEN }}@github.com/<yourname>/<yourname>.github.io.git >/dev/null 2>&1
git push --force deploy gh-pages >/dev/null 2>&1
Fixing issues with GitHub Actions
There are a few things to tackle, however, as GitHub Actions works differently than Travis CI.
First, the GitHub-provided token, for unknown reasons, could not trigger GitHub Pages deploys. This used to be the case1 but has since been (partially) fixed. Now it can trigger Pages for non-root commits to the Pages branch. A “root commit” is the sole commit on a new branch, like the one created by the above build script, which always initializes a new repository and creates a single commit for the contents. This Pages issue makes the above build script non-functional, and we need to fix it.
An easy solution is to fetch the target (deploy) branch, and add a commit on top of whatever’s there already. So we modify the build script to include this fix:
- name: Deploy site
run: |
cd _site
git init
git config user.name "GitHub"
git config user.email "[email protected]"
git remote add deploy https://${{ secrets.GITHUB_TOKEN }}@github.com/<yourname>/<yourname>.github.io.git >/dev/null 2>&1
git fetch --depth=1 deploy gh-pages
git reset --soft FETCH_HEAD
git checkout -B gh-pages
git add --all
git commit --message "Auto deploy from GitHub Actions build $GITHUB_RUN_NUMBER"
git push deploy gh-pages >/dev/null 2>&1
In this revised script, we first fetch the target branch, with depth set to 1 to avoid unnecessary downloads. Then we reset our “branch pointer” to the fetched branch (FETCH_HEAD
), before finally adding our content as another commit on top of it.
Fixing issues with GitHub Actions - Alternative approach
There’s an alternative solution to this issue, by cloning the deploy repository beforehand (and remove git init
from the deploy step).
Insert this “clone” step before the “build” step:
- name: Prepare build
run: |
git clone -q --depth=1 --branch=gh-pages --single-branch --no-checkout \
https://${{ secrets.GITHUB_TOKEN }}@github.com/<yourname>/<yourname>.github.io.git _site/
and change the deploy step to this:
- name: Deploy site
run: |
cd _site
git config user.name "GitHub"
git config user.email "[email protected]"
git add --all
git commit --message "Auto deploy from GitHub Actions build $GITHUB_RUN_NUMBER"
git push deploy gh-pages
An important note is that you should tell Jekyll to keep your .git
folder in the _site
directory when building your site. This can be done by adding the following settings to your _config.yml
:
keep_files: [.git]
I recall that Jekyll 4.0 has this setting emplaced by default, but can’t find the reference for now, so I’m recommending that you explicitly write this into your config file even if you have Jekyll 4 locally (which you probably don’t if you’re using the github-pages
gem). It’s a good idea to write configurations explicitly, after all.
Finally
Now then, why did I migrate my website build to GitHub Actions, if both Travis CI and CircleCI are running perfectly?
I chose to do so for the following reasons:
- It’s free for public repositories, with unlimited total usage. One rarely hits the total usage quota, however, even with CircleCI, which has
a monthly limit of 1,000 total run minutes(Update: It’s now 250 minutes weekly).- CircleCI’s limit applies at account level, and does not differentiate between public and private repositories.
- November 2020: Travis CI’s new pricing model and recent service degradations aren’t particularly interesting to learn about.
- Better runtime environments, except for CPU power, which is only slightly slower that that on CircleCI.
- Boots faster, runs faster, more memory
- It’s provided by GitHub and hosted by Microsoft Azure, which may be more trusted than Travis CI and CircleCI for some users.
- One less external service to depend on. No more need to log into a separate website to review logs.
- … and more
The primary downside compared to Travis CI is increased build config complexity, but on the other hand it adds more flexibility to your build patterns, which reciprocates.
But the most important thing to note is that whatever others tell, you should try and find the one most suitable for you.
Leave a comment