Deploying a Static Website with Jekyll and GitHub Actions
24 Aug 2023This website is served using Jekyll, a static site generator, and hosted directly from a GitHub repository using Pages. While I won’t really get into any Jekyll details, I will describe how the site is built and deployed using custom GitHub Actions.
GitHub Pages
While Pages can handle building the static site itself, my process is made more complicated by the fact that I use a few Jekyll plugins that are not included in the standard process. For the longest time I had been building the static site locally and storing only the final, static html in the repository—the static html could then be trivially deployed by Pages. While this works, it makes keeping the source site and the static site in sync an annoying process. To combat this I actually maintained two repositories for each, however inevitably I would forget to push one of them (and even worse often follow that up by making further changes on an outdated copy on another machine).
Luckily, deployment to Pages can now be done using GitHub Actions which allows the build process to be customized. While this deployment process is currently in Beta, it’s been rock solid for me so far. This means I can instead maintain a single repository for the source site and build and deploy the static site whenever I push to GitHub.
Specifying Jekyll Dependencies
First I’ll assume we are in a directory that has been initialized with a Jekyll
site (for more information see the Jekyll Quickstart). We
can then create a file Gemfile
at the base directory specifying the necessary
gems (i.e. packages) to build the site. For more information see Jekyll’s
deployment documentation, however mine looks something like:
source "https://rubygems.org"
gem "jekyll"
group :jekyll_plugins do
gem "jekyll-scholar"
gem "jekyll-sitemap"
# ... any other jekyll plugins.
end
Here any gems specified in the :jekyll_plugins
group will be automatically
included by Jekyll when it builds the site and as a result they do not need to
be specified elsewhere in the configuration. Next we can install and record
these gems by running
bundle install
bundle lock --add-platform=x86_64-linux
This will ensure that all of the relevant gems are installed as well as creating
Gemfile.lock
which records the name and version of each gem (including their
dependencies). The second of these two commands will also include any
platform-specific gems for x86_64-linux
which is what we will use when
building on GitHub. While not necessary now, running bundle update
will update
the installed gems and record them in the lockfile.
Creating the Deployment Action
We can add a deployment action by creating
.github/workflows/build-and-deploy.yml
(the name of the file is arbitrary)
with the following contents:
name: Build and deploy
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 'ruby'
bundler-cache: true
- name: Build site
run: bundle exec jekyll build
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v2
deploy:
needs: build
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: $
runs-on: ubuntu-latest
steps:
- name: Deploy to Pages
uses: actions/deploy-pages@v2
This defines two jobs, build and deploy, which run in sequence on every push to
the master branch. One of the keys to the build job is that while it installs
ruby and all the packages specified in the lockfile, the bundler-cache
line
ensures that the gems are cached between runs. This dramatically speeds up the
runtime of the job. The final step of the build job creates an intermediate
artifact that will be used by the deploy job to upload the site, albeit only
after the build job finishes successfully.
Finishing up
If we ignore all the other Jekyll-specific files, we should now have a directory with the following structure:
.github/workflows/build-and-deploy.yml
Gemfile
Gemfile.lock
...
All that’s left to do is create the GitHub repository, e.g.
https://github.com/USER/REPO
, and enable GitHub Actions as the build and
deployment source. This can be done by updating the settings at
https://github.com/USER/REPO/settings/pages
. With that in place, and assuming
the above files are checked into the repository, the next push should build and
deploy the site to https://USER.github.io/REPO
Note: by naming the repository USER.github.io
this creates a user site which
will deploy to https://USER.github.io
.