From CircleCI to GitHub Actions for Jekyll publishing
- 📅
- 📝 1,274 words
- 🕙 6 minutes
- 📦 Development
- 🏷️ Jekyll, GitHub Actions, CI
I’ve been a big fan of static site generation since I switched from WordPress to Jekyll back in 2018. I’m also a big fan of learning new technologies as they come along, and now GitHub Actions are out in the wild; I thought this would be an opportunity to see how I can port my existing custom CircleCI build to Jekyll.
The CircleCI job
A quick recap from part 5 — Hosting & Building, my CircleCI configuration was basically two jobs that have subsequently been tweaked since then. They are:
Build
The build job’s responsibility was to configure a Ruby environment capable of executing Jekyll to build the site, removing the .html extension from the output filenames, and then indexing the content using Algolia. Here is the start of the .circleci/config.yml
I was using for that:
{%raw%}version: 2
jobs:
build:
docker:
- image: circleci/ruby:2.6.1
working_directory: ~/jekyll
environment:
- JEKYLL_ENV=production
- NOKOGIRI_USE_SYSTEM_LIBRARIES=true
- JOB_RESULTS_PATH=run-results
steps:
- checkout
- restore_cache:
key: jekyll-{{ .Branch }}-{{ checksum "Gemfile" }}
- run:
name: Update gems
command: gem update --system
- run:
name: Install dependencies
command: bundle check --path=vendor/bundle || bundle install --path=vendor/bundle --jobs=4 --retry=3
- save_cache:
key: jekyll-{{ .Branch }}-{{ checksum "Gemfile" }}
paths:
- "vendor/bundle"
- run:
name: Create results directory
command: mkdir -p $JOB_RESULTS_PATH
- run:
name: Build site
command: bundle exec jekyll build --config _config.yml,_config-publish.yml 2>&1 | tee $JOB_RESULTS_PATH/build-results.txt
- run:
name: Remove .html suffixes
command: find _site -name "*.html" -not -name "index.html" -exec rename -v 's/\.html$//' {} \;
- run:
name: Index with Algolia
command: bundle exec jekyll algolia --config _config.yml,_config-publish.yml
- store_artifacts:
path: run-results/
destination: run-results
- persist_to_workspace:
root: ~/jekyll
paths:
- _site
{%endraw%}
Deploy
Deploy takes the output from the build job and syncs it with the S3 bucket I use to publish the site. It then applies AWS S3-specific commands using the AWS CLI tool to ensure metadata, redirects, and caching are correctly set using a Python environment.
{%raw%} deploy:
docker:
- image: circleci/python:2.7
working_directory: ~/jekyll
steps:
- attach_workspace:
at: ~/jekyll
- run:
name: Install AWS CLI
command: sudo pip install awscli
- run:
name: Deploy to S3
command: aws s3 sync _site s3://damieng-static/ --delete --content-type=text/html
- run:
name: Correct MIME for robots.txt automatically
command: aws s3 cp s3://damieng-static/robots.txt s3://damieng-static/robots.txt --metadata-directive="REPLACE"
- run:
name: Correct MIME for sitemap.xml automatically
command: aws s3 cp s3://damieng-static/sitemap.xml s3://damieng-static/sitemap.xml --metadata-directive="REPLACE"
- run:
name: Correct MIME for Atom feed manually
command: aws s3 cp s3://damieng-static/feed.xml s3://damieng-static/feed.xml --no-guess-mime-type --content-type="application/atom+xml" --metadata-directive="REPLACE"
- run:
name: Redirect /damieng for existing RSS subscribers
command: aws s3api put-object --bucket damieng-static --key "damieng" --website-redirect-location "https://damieng.com/feed.xml"
- run:
name: Latest Envy Code R redirect
command: aws s3api put-object --bucket damieng-static --key "envy-code-r" --website-redirect-location "https://damieng.com/blog/2008/05/26/envy-code-r-preview-7-coding-font-released"
- run:
name: Latest Envy Code R redirect #2
command: aws s3api put-object --bucket damieng-static --key "fonts/envy-code-r" --website-redirect-location "https://damieng.com/blog/2008/05/26/envy-code-r-preview-7-coding-font-released"
- run:
name: Latest Envy Code R download
command: aws s3api put-object --bucket damieng-static --key "downloads/latest/EnvyCodeR" --website-redirect-location "https://download.damieng.com/fonts/original/EnvyCodeR-PR7.zip"
- run:
name: Correct MIME for CSS files
command: aws s3 cp s3://damieng-static/css/ s3://damieng-static/css/ --metadata-directive="REPLACE" --recursive
{%endraw%}
GitHub Actions
So how could I go about this in GitHub Actions? I have to admit I spent far too long poking around and examining existing Jekyll actions. I have a bunch of steps here I need fine control of, especially around Algolia and S3. I finally ended up on what was quite a simple port.
Unlike the CircleCI configuration, I did not split these into two separate jobs because:
- There is no exact equivalent to
persist_to_workspace
andattach_workspace
- The alternative of storing and restoring the artifacts leaves large useless artifacts around
- I never ended up running the jobs separately
- GitHub Actions provides an environment with both Ruby and AWS CLI installed
So, on to the configuration which lives in .github/workflows/jekyll.yml
in my case:
{%raw%}name: Build site and deploy
on:
push:
branches: [ master ]
jobs:
build:
name: Build + Deploy
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.6.1
- name: Ruby gem cache
uses: actions/cache@v1
with:
path: vendor/bundle
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gems-
- name: Install dependencies
run: |
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3
- name: Build Jekyll site
run: bundle exec jekyll build --config _config.yml,_config-publish.yml
- name: Remove .html suffixes except for index.html
run: find _site -name "*.html" -not -name "index.html" | while read f; do mv "$f" "${f%.html}"; done
- name: Index with Algolia
env:
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
run: bundle exec jekyll algolia --config _config.yml,_config-publish.yml
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Sync site with S3
run: aws s3 sync _site s3://damieng-static/ --delete --content-type=text/html
- name: Correct MIME types
run: |
aws s3 cp s3://damieng-static/ s3://damieng-static/ --exclude "*" --include "*.txt" --metadata-directive="REPLACE"
aws s3 cp s3://damieng-static/sitemap.xml s3://damieng-static/sitemap.xml --metadata-directive="REPLACE"
aws s3 cp s3://damieng-static/feed.xml s3://damieng-static/feed.xml --no-guess-mime-type --content-type "application/atom+xml" --metadata-directive "REPLACE"
aws s3 cp s3://damieng-static/css/ s3://damieng-static/css/ --metadata-directive "REPLACE" --recursive
aws s3 cp s3://damieng-static/js/ s3://damieng-static/js/ --metadata-directive "REPLACE" --recursive
- name: Redirect /damieng for existing RSS subscribers
run: aws s3api put-object --bucket damieng-static --key "damieng" --website-redirect-location "https://damieng.com/feed.xml"
- name: Latest Envy Code R redirects
run: |
aws s3api put-object --bucket damieng-static --key "fonts/envy-code-r" --website-redirect-location "https://damieng.com/blog/2008/05/26/envy-code-r-preview-7-coding-font-released"
aws s3api put-object --bucket damieng-static --key "downloads/latest/EnvyCodeR" --website-redirect-location "https://download.damieng.com/fonts/original/EnvyCodeR-PR7.zip"
- name: Set caching for images at 30 days
run: aws s3 cp s3://damieng-static/ s3://damieng-static/ --exclude "*" --include "*.svg" --include "*.ico" --include "*.jpg" --include "*.png" --include "*.webp" --include "*.gif" --recursive --metadata-directive REPLACE --expires 2034-01-01T00:00:00Z --acl public-read --cache-control max-age=2592000,public
- name: Set caching for CSS and JS at 1 hour
run: aws s3 cp s3://damieng-static/ s3://damieng-static/ --exclude "*" --include "*.css" --include "*.js" --recursive --metadata-directive REPLACE --expires 2034-01-01T00:00:00Z --acl public-read --cache-control max-age=3600,public
{%endraw%}
I also took the opportunity to fix a long-running issue in that my S3 objects would lose my manually-applied cache settings (and new posts and files would not have any).
Gotchas
There were only a couple of bumps in the road once I decided on a straight-port rather than trying to leverage higher-level existing actions:
- The secrets configured in the repo settings were not automatically exposed to the commands running in the action. Instead, you have to expose them using the
${{ secrets.KEY_NAME }}
syntax. - I was using
rename
instead ofmv
. I don’t recall why. Perhaps it was my Windows-ness creeping in. Rename has been dropped in newer distros.
Summary
The syntax is surprisingly similar with build-times about the same as CircleCI. It’s just nice to have it in one place. Time to port some other repos over!
[)amien
0 responses to From CircleCI to GitHub Actions for Jekyll publishing