Own

Dependencies steps:

gem update --system --no-document
gem update bundler --no-document
bundle config set path vendor/bundle
bundle install --jobs 4 --retry 3 --frozen
bundle clean

Avoid updating the Gemfile.lock file with --frozen flag.

The clean command removes unused gems. I don’t see it in bundle -h output though.

Use PeaceIris action to handle the commit part, instead of the detailed committing in the next example.

Consider for Jekyll 4 cache, also caching .jekyll-cache/ for faster builds. Or let the remote build be slower and let the cached build locally be fast.

Jekyll docs

This example is based on a PR for building Jekyll’s own docs site at jekyllrb.com, using GH Actions instead of plain GH Pages flow. The code is very low-level for more control but this makes it so not reusable. There are further notes at the end of this page on this. You may want to take pieces of this recipe rather than the whole thing.

I don’t know why cache keys is too items. I usually see it as one or two lines in a single string.

Note use of GITHUB_TOKEN near the end.

  • docs.yaml
      name: Build and deploy Jekyll documentation site
    
      on:
        pull_request:
          branches:
            - master
    
      env:
        RUBY_VERSION: 2.7
    
      jobs:
        deploy_docs:
          if: "!contains(github.event.commits[0].message, '[ci skip]')"
    
          runs-on: 'ubuntu-latest'
    
          steps:
            - uses: actions/checkout@v2
    
            - uses: actions/setup-ruby@v1
              with:
                ruby-version: ${{ env.RUBY_VERSION }}
    
            - name: Set up cache for Bundler
              id: cache
              uses: actions/cache@v2
              with:
                path: vendor/bundle
                key: ${{ runner.os }}-bundler-${{ env.RUBY_VERSION }}-${{ hashFiles('Gemfile') }}-${{ hashFiles('jekyll.gemspec') }}
                restore-keys:
                  - ${{ runner.os }}-bundler-${{ env.RUBY_VERSION }}-${{ hashFiles('Gemfile') }}-
                  - ${{ runner.os }}-bundler-${{ env.RUBY_VERSION }}-
    
            - name: Set up dependencies
              run: |
                gem update --system --no-document
                gem update bundler --no-document
                bundle config set path vendor/bundle
                bundle install --jobs 4 --retry 3
                bundle clean
    
            - name: Clone target branch
              run: |
                REMOTE_BRANCH="${REMOTE_BRANCH:-gh-pages}"
                REMOTE_REPO="https://${GITHUB_ACTOR}:${{ secrets.GITHUB_TOKEN }}@github.com/${GITHUB_REPOSITORY}.git"
                echo "Publishing to ${GITHUB_REPOSITORY} on branch ${REMOTE_BRANCH}"
                rm -rf docs/_site/
                git clone --depth=1 --branch="${REMOTE_BRANCH}" --single-branch --no-checkout \
                  "${REMOTE_REPO}" docs/_site/
    
            - name: Build site
              run: bundle exec jekyll build --source docs --destination docs/_site --verbose --trace
              env:
                # For jekyll-github-metadata plugin.
                JEKYLL_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    
            - name: Deploy to GitHub Pages
              run: |
                SOURCE_COMMIT="$(git log -1 --pretty="%an: %B" "$GITHUB_SHA")"
                pushd docs/_site &>/dev/null
                : > .nojekyll
                git add --all
                git -c user.name="${GITHUB_ACTOR}" -c user.email="${GITHUB_ACTOR}@users.noreply.github.com" \
                  commit --quiet \
                  --message "Deploy docs from ${GITHUB_SHA}" \
                  --message "$SOURCE_COMMIT"
                git push
                popd &>/dev/null
    

Notes on usage

Note these steps around Bundler and committing and publishing to GH Pages are very low-level. I wouldn’t use this across many projects myself as it would be tedious to maintain and not DRY and also I do not care about this level of control when using a single action for whole flow meets my needs. However, I might use the first few steps to manage cache, dependencies and building the site, but then rely on a more generic Action to handle committing to the gh-pages branch.

The detailed steps are preferred by the maintainers, when I brought it up on the PR. The comment was that this provides more control over logging and commit messages. At the cost of having to maintain boilerplate instead of having it maintained in a separate action (which means less control and a different kind of risk of commands going out of date or security issues). I think it also reduces dependency on actions which might not be maintained or that do things it a way that is not ideal here.

These steps are also intended to be used by GH and Jekyll experts and also involves a lot of boilerplate which has to be maintained. So for other projects it may make sense to use existing actions and therefore keep your workflow file shorter and simpler, and easier to maintain across many Jekyll projects. The burden of maintaining the code (for example as Bundler CLI changes) and making good choices (around Ruby, Bundler, publishing) then falls on action maintainers, which is a different kind of risk but less effort to use.