Related recipe - Jekyll in the Container section.

This flow runs a Docker container. If you don’t want to use Docker, see Jekyll Ruby Action, which is a light approach.

It runs Jekyll inside a container that is run as a binary (using docker CLI but no Dockerfile needed). So it can be used as a drop-in replacement for running jekyll directly.

Plus, the container takes care of Ruby and Jekyll dependencies and installing project gems. This builder image even includes Node.js.

Based on jekyll.yml - a starter workflow provided by GitHub.

  • main.yml
      name: GH Pages deploy
    
      on:
        push:
          branches: main
          paths-ignore:
            - README.md
    
        pull_request:
          branches: main
          paths-ignore:
            - README.md
    
      jobs:
        build-deploy:
          name: Build and deploy
    
          runs-on: ubuntu-latest
    
          steps:
            - name: Checkout 🛎️
              uses: actions/checkout@v2
    
            - name: Build Jekyll site
              run: |
                docker run \
                  -v ${{ github.workspace }}:/srv/jekyll \
                  jekyll/builder:4.2.0 \
                  /bin/bash -c 'chmod 777 /srv/jekyll && bundle exec jekyll build --future'
    
            - name: Deploy to GH Pages 🚀
              if: ${{ github.event_name != 'pull_request' }}
              uses: peaceiris/actions-gh-pages@v3
              with:
                github_token: ${{ secrets.GITHUB_TOKEN }}
                publish_dir: _site
    

You can use jekyll COMMAND or bundle exec jekyll COMMAND. The latter is probably safer, using the project Jekyll instead of the global Jekyll (at least global inside the container).

Make sure that the image tag Jekyll version matches the Jekyll version in Gemfile. Though, you could even leave Jekyll out of Gemfile, but I wouldn’t recommend it, so you can still run it outside a container.

See jekyll/builder tags available.

If you want to use say Jekyll 4.3 in your Gemfile and the Docker image tag is not available, you could use bundle exec jekyll build as the command passed to the container. To ensure the project Jekyll gets used, instead of the container’s global Jekyll.

About the approach

This builds the site using a Docker container step. This means you don’t need to use any Action from the marketplace to set up your Ruby and Jekyll environment. The container image is ready to go. When you run it, it will even install your gems for you using Bundler.

I think this is great - I don’t know why there are so many Jekyll actions out there. Maybe if you really need control over the environment or build steps. Some actions include Jekyll and GH Pages in one - I don’t think it is a good idea to mix those together in one inseparable step.

Another advantage is that you can run the exact same container locally - which is great for debugging and for running your application locally on any OS without requiring Ruby, Bundler and gems installed.

An advantage of using Docker here is that you can run the same command locally - no Ruby or Jekyll needed. You can move the full docker command to a Makefile too to make it easier to run as make build.

Caching note

A disadvantage of using Docker here is that the image is not cached and so must be downloaded each time. I found an action which supports caching of Docker images, but adds extra steps and complexity. Maybe the plain cache action can work for Docker cache directory?

Also, the gems have to be downloaded and installed each time, which be avoided by adding the cache action, even if it doesn’t cache the image.

Notes on the workflow

  • Using the Docker step alone output would get thrown away after the build. If you want to persist as a GH Pages site, just add on the last section as below, using a generic GH Pages action.
  • You can pick a tag like 4 to get a recent version in 4.x range. I’d avoid latest as one day you’ll suddenly get 5. GH Pages normally limits you to 3.9.
  • The --future flag is to publish future-dated posts.

Original workflow

The workflow is based on the one generated by the GitHub when it suggests a Jekyll workflow for your project.

My changes

  • Use Jekyll version 4 instead of latest, to avoid upgrading to 5 by accident. Also, you can use 4.2 etc. if such as tag exists.
  • Add GH Pages at the end to persist on gh-pages branch. I’ve also set up this deploy step only runs on the main branch, not feature branches.
  • Remove a volume.

I tried to use $PWD instead of ${{ github.workspace }} I but got a permissions error. Just make sure to use $PWD for local use. i.e. -v "$PWD:/srv/jekyll"

See related notes below.

Permissions note

Note use of chmod 777 for the directory (not for the files in it).

If this is not included, then the container logs that it is installing gems which fails because of no permissions to write to Gemfile.lock.

There was an error while trying to write to `/srv/jekyll/Gemfile.lock`. It is

I don’t know how this works though. The entry-point command given to the container is only meant to run after the docker has set up gems internally.

Volume note

Note that GitHub’s own starter workflow mounts two volumes, when only one is needed.

Here it is anyway with _site as a volume.

-v ${{ github.workspace }}:/srv/jekyll \
-v ${{ github.workspace }}/_site:/srv/jekyll/_site \

The second is unnecessary as it is already covered by the first as a nested directory with the identical name in and outside the container.