Quickie: Build & Deploy Jekyll Site with GitHub Actions

Since GitHub Actions is now a thing and setting up a CI workflow is as easy as ever, why not take 10 minutes to make publishing a new article in Jekyll as easy as pushing to master?

Also this gets around the GitHub pages builder, which limits your plugin choices.

Creating a workflow

In your repo’s root folder, create the .github/workflows folder. Your GitHub Action workflows will live here.

Create a yml file inside this folder, like publish.yml. Start the file with:

name: Publish Jekyll Site

Triggers

We’re going to trigger this workflow every time we push to master. So continue publish.yml with:

on:
  push:
    branches:
      - master

Reference: Events that trigger workflows

Jobs

Let’s start to tell GitHub what to do after this workflow is triggered.

jobs:
  jekyll-build-deploy:
      runs-on: ubuntu-latest
      steps:
        - uses: actions/checkout@v2
        # More steps after this...

Here we’ve created a job named jekyll-build-deploy. runs-on: ubuntu-latest tells GitHub which environment to use. steps is where we’ll start to specify what to do in a job.

Building the site

Checkout our code

First we need to get our source code from our repo.

- uses: actions/checkout@v2

This checkouts our code from the repository into the workspace. More Info

Cache dependencies

To speed up our build, we’ll cache our bundler dependencies between runs.

- uses: actions/cache@v2
  with:
    path: vendor/bundle
    key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
    restore-keys: |
      ${{ runner.os }}-gems-

This tells CI to retrive vendor/bundle if it’s cached, and cache vendor/bundle after this run finishes. More Info

Install dependencies

First we need to setup the virtual environment for ruby:

- uses: actions/setup-ruby@v1
  with:
    ruby-version: '2.6'

Now bundle should be available, let’s install our dependencies:

- name: Install dependencies
  run: |
    bundle config path vendor/bundle
    bundle install --jobs 4 --retry 3

Here we’re giving this step a name using name: ..., and also run: | to run multiple commands.

Build webpages using jekyll

- name: Build site
  env:
    JEKYLL_ENV: production
  run: bundle exec jekyll build

Here using env we’re telling Jekyll to build for a production environment. After this step finishes, inside _site should be our built static website.

Deploying the site

Here are a few ways to deploy, depending on your hosting service.

rclone

If your hosting service support rclone full list, like AWS S3 or Aliyun OSS (what I’m using), you could use the wei/rclone@v1 action to deploy.

  1. Setup your rclone config locally (see above list for docs for each provider)
  2. rclone config show <config-name> to print out your config
  3. Go to your repository -> Settings -> Secrets, create a new secret named RCLONE_CONF, and paste your config (from step 2) inside the value section. Here’s a nice step-by-step guide.
  4. Update your workflow file
- name: rclone to whatever
  uses: wei/rclone@v1
  env:
    RCLONE_CONF: ${{ secrets.RCLONE_CONF }}
  with:
    args: sync _site config-name:dest

Here I’m using rclone sync to sync everything inside _site to config-name:dest.

GitHub Pages

Using peaceiris/actions-gh-pages@v3 action (More Info):

- name: Deploy to GitHub Pages
  uses: peaceiris/actions-gh-pages@v3
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    publish_dir: _site

Or you could deploy to another repoistory.

Or literally anything else

Want to build a docker image and push it to Docker Hub? Want to archive all versions of your site? Want to SSH to your server and restart a service? As long as your deployment can be done using a script, you can just put it in a run: | step.

Also, there is a good chance other people already tried this before you, check out GitHub Marketplace for user created actions.

Profit

This is what the finished publish.yml should look like:

name: Build Jekyll & Publish to OSS
on:
  push:
    branches:
      - master
jobs:
  jekyll-oss:
      runs-on: ubuntu-latest
      steps:
        - uses: actions/checkout@v2

        - uses: actions/cache@v2
          with:
            path: vendor/bundle
            key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
            restore-keys: |
              ${{ runner.os }}-gems-

        - uses: actions/setup-ruby@v1
          with:
            ruby-version: '2.6'

        - name: Install dependencies
          run: |
            bundle config path vendor/bundle
            bundle install --jobs 4 --retry 3

        - name: Build site
          env:
            JEKYLL_ENV: production
          run: bundle exec jekyll build

        - name: rclone to OSS
          uses: wei/rclone@v1
          env:
            RCLONE_CONF: ${{ secrets.RCLONE_CONF }}
          with:
            args: sync _site oss:bucket

Commit this file, and now make some changes to your site, commit & push your changes to master.

In “Actions” tab in your repository you should now see your latest commit triggering a run.

Actions Log

Also, due to caching, the second run should be a lot faster than the first.

What else

  • You can try to split the build steps and deploy steps into separate jobs, using artifacts. More Info
  • You can use a self-hosted runner, if you somehow ran out the 2000 minutes quota. More Info
  • You can try to create your own action (checking the site, deploying to some service, …), so the community can easily use them in their own workflows. More Info
  • Replace sensitive information with secrets. More Info
  • … and many more