Deploy Jekyll blog with Github Actions
Github Actions is a dev/ops automation service that has recently became generally available to public after quite a long beta period. Additionally, the workflow files that define actions underwent a major change from HCL format to the more widely adopted YAML, which I'd argue is better for readability. I have used Actions for various tasks while in beta for things like building Go binaries and find it pretty useful since there's no need to involve a thid party service, the only thing you really need is Docker.
Today i'm going through a pretty basic build and deployment pipeline for a Jekyll blog. In fact, this very blog is powered by Jekyll and I've been pretty happy with the setup over the years after migrating from Wordpress. There is nothing exotic about the pipeline and I'm sure a lot of people are aware of services like Netlify (i'm a customer too!) that automate static site deployments from git providers like Github or Gitlab.
The way I publish my blog posts is pretty simple: create a post file, get the article going, fix typos and then finally preview the page before I decide to roll it out. The blog is hosted on a cheap Digital Ocean instance which I happen to use for other things. Usually I don't remember which commands are needed run to deploy so i created a simple rake task, but the meat of it is:
desc "Clean site"
task :clean do
sh "rm -rf ./_site"
end
desc "Build site"
task build: :clean do
sh "bundle exec jekyll build"
end
desc "Deploy site"
task :deploy do
exec "rsync -rtzh -e 'ssh -p PORT' --progress --delete _site/ USER@sosedoff.com:PATh"
end
Now, let's implement the Github Actions workflow. First, create a new directory in
your blog repository .github
. We plan on running automatic deploys on every push
to master branch. We would also like to receive a Slack message when deploy is complete.
The workflow file should look like this:
name: deploy
on:
push:
# Action run will only be triggered on updates to master branch
branches:
- master
jobs:
run-deploy:
runs-on: ubuntu-latest
steps:
# Check out git repository
- name: checkout
uses: actions/checkout@master
# Install dependencies
- name: dependencies
uses: docker://ruby:2.5
env:
BUNDLE_PATH: .bundle
with:
entrypoint: bundle
args: install -j=4
# Build Jekyll site
- name: build
uses: docker://ruby:2.5
env:
BUNDLE_PATH: .bundle
LANG: en_US.UTF-8
LANGUAGE: en_US.UTF-8
LC_ALL: C.UTF-8
with:
entrypoint: bundle
args: exec rake build
# Publish blog to DigitalOcean
- name: publish
uses: ./.github/rsync
env:
JEKYLL_DEPLOY_KEY: ${{ secrets.JEKYLL_DEPLOY_KEY }}
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
DEPLOY_DIR: ${{ secrets.DEPLOY_DIR }}
# Send me a slack notification
- name: notify
uses: ./.github/slack
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
with:
args: Blog has been deployed
Jekyll steps are executed using the offical ruby 2.5 image, this is mostly done since
I want to be able to customize any command without forking the step. As for the
publish step - it grabs the _site
directory produced by the build
step and uploads
it to the server using rsync
, a tool to synchronize files between machines. There'a
a custom action for this step, create a directory .github/rsync
with the following
files.
Dockerfile:
FROM alpine:3.6
RUN \
apk update && \
apk add --no-cache rsync openssh-client && \
rm -rf /var/cache/apk/*
ADD entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
entrypoint.sh:
#!/bin/sh
# Save private ssh key from env var
echo $JEKYLL_DEPLOY_KEY | base64 -d > /key
chmod 0600 /key
rsync \
-e "ssh -i /key -o StrictHostKeyChecking=no -p $DEPLOY_PORT" \
-rtzh \
_site/ \
--progress \
$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_DIR
Slack notification action is pretty small, go ahead and copy .github/rsync/Dockerfile
into .github/slack/Dockerfile
and add a new entrypoint.sh
file:
#!/bin/bash
curl -X POST $SLACK_WEBHOOK -d "{\"text\": \"$*\"}"
Majority of the configuration is done via environment variables (like JEKYLL_DEPLOY_KEY
).
They are set in Github settings interface below:
Once everything is setup go ahead and commit changes to the workflow files and do a git push to master branch (or whatever branch you specify in workflow file). Successful result will look like the screenshot below:
And that's it! Of course I could have created an Action specifically tailored for Jekyll deployments (Action Marketplace might have some already) but this post illustrates that creating your own actions and customizing them with exactly what you need is not hard or complicated at all. The setup described in this blog post could easily be tweaked to support other static website generator tools like Middleman or Gastby.
UPDATE: I went ahead and refactored out all the code and merged everything into a single docker image. Check out source on Github