Using Github Actions to build a Jekyll 4 site and host it on Github Pages
A while back I decided that I was going to move this site from an older Lektor setup to Jekyll while retaining my Github Pages hosting setup. The reasons behind the move are little more than personal preference, but I had hoped to at least rid myself of Travis CI after being unhappy with Idera’s acquisition of it.
I initially planned on taking advantage of the automatic building and hosting of a Jekyll site on Github Pages, however after seeing that I wouldn’t be able to have custom build steps such as using PostCSS for a custom theme and being locked into Jekyll 3, I had to find my own way for building and deploying the site. Thankfully that’s an easy task now as a result of Github Actions which let me both ditch Travis CI and not have to worry about getting another CI runner authorized to push to the repo.
A lot of my setup follows the footsteps of David Stosik’s setup, so be sure to give that a read. Additionally, I’m actively modeling my photography page setup after Brandur’s setup and his documentation around how his site handles them. I’ll have a follow up post on how the photography side of things works once I get it settled.
Github Actions
If you would like to follow along while reading through my own workflow configuration, you can find it here.
One of the great things about Github Actions over third-party CI providers is the deep integration with the repository: you can clone, commit and push all out of the box without many issues. This, combined with the reusable and composable actions make it quick and easy to build up powerful workflows.
I should note that these days there are two actions that could be used to simplify some of this:
These actions were not around when I started this, and I’m choosing not the migrate over because the setup is simple and I don’t feel that these actions add any value to my setup.
Before we get too far, a small note of how we’ll set things up today: There will be two branches in git, master
and source
. The root /
directory in the master
branch is where the built Github Pages will be served from, and source
stores the Jekyll setup. While we could have the pages served from _site/
in my source
branch, this means that every build will commit to my working branch, leading to some pains with uncesscessary commits on the working branch.
For some different configuration options, take a look at these docs.
Thi decision to have two branches means that we’ll need to:
- Pull from
source
- Build Jekyll
- Commit and Push the built
_site/
directory tomaster
on:
push:
branches:
- source
The first thing we want to do is to limit the workflow to only run when I push to the source
branch, so that we don’t get our CI into a loop. With Github Actions we do this with the on.push.branches
setting:
jobs:
github-pages:
runs-on: ubuntu-latest
Next, I personally want to run this on a familiar environment, so we’ll use the built in Github Actions ubuntu
runner, by setting the jobs.<job-id>.runs-on
setting (more info on the available runners here.
The rest of our work will be in the jobs.<job-id>.steps
:
steps:
- uses: actions/checkout@v2
with:
ref: source
path: source
- uses: actions/checkout@v2
with:
ref: master
path: build
These two actions are how we’ll pull from one branch and push to another, doing two checkouts of the repository at different paths for each branch: The source
branch that’ll we’ll use to build the Jekyll site lives at ./source
and the master
branch at ./build
.
- uses: actions/setup-ruby@v1
with:
ruby-version: '2.7'
- uses: actions/setup-node@v1
with:
node-version: '16.4.x'
We’ll need ruby, and my own setup needs node so lets get those installed.
- uses: actions/cache@v2
with:
path: source/vendor/bundle
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gems-
- uses: actions/cache@v2
with:
path: source/node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-npm-
- name: Bundle install
working-directory: source
run: |
gem install bundler --no-document
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3
- name: Yarn install
working-directory: source
run: |
yarn install
And now we’ll install the dependences in ./source
. We’ll use the cache actions to ensure that our dependencies are quick to install leading to faster publishing, and use the hash of the lockfiles to determine which cache to restore.
Now we get to the heart of the workflow, building and publishing the site:
- name: Jekyll Build
working-directory: source
env:
JEKYLL_ENV: production
run: bundle exec jekyll build -d ../build
Instead of building to Jekylls default _site
, we’ll have it build to our checked out master
branch, at ../build
from inside of ./source
.
And finally we’ll publish the site by making a new commit to master
and push it to the repository with a helpful commit message linking the build back to the source
commit that triggered it, and set the commiter to show up as your own user on Github:
- name: Commit and push changes to master
working-directory: build
run: |
git config user.name "${GITHUB_ACTOR}"
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
git add .
git commit -m "Update GitHub pages from Jekyll build for commit ${GITHUB_SHA}"
git push origin master
And that’s it! It might look like a lot all spaced out like this, but it’s a fairly simple setup that is still powerful and expandable as you add more features to your site such as photograph processing, javascript resource processing or html cleanup in ./build
before publishing.