Building a CI/CD pipeline for my Jekyll website

5 minute read

Published:

The website you’re on right now is not managed by a content management system. All content is written in Markdown. Before executing the steps in this blog post I had to change the Markdown files locally, upload them to this server and execute a jekyll build to deploy the website. These are manual steps that can easily be automated. In this blog post I will explain how i used git to build following continuous integration and deployment (CI/CD) pipeline for my Jekyll site:

Git based CI/CD Pipeline for a Jekyll site

Pre-requisites

  1. You have a Jekyll site deployed on your server. If you don’t, follow this guide: Step by Step Jekyll site tutorial.

Creating the remote repository:

Start with installing git on the server:

sudo apt-get install git-all

Verify if git is succesfully installed with:

git version

Create a new directory in your home directory:

mkdir website.git

Initialize a ‘bare’ git repository in website.git:

git init --bare website.git

After executing this command, execute an ls -l website.git. The result should be:

-rw-rw-r--  1 xxx xxx   23 Nov 24 19:11 HEAD
drwxrwxr-x  2 xxx xxx 4096 Nov 24 19:11 branches/
-rw-rw-r--  1 xxx xxx   66 Nov 24 19:11 config
-rw-rw-r--  1 xxx xxx   73 Nov 24 19:11 description
drwxrwxr-x  2 xxx xxx 4096 Nov 24 19:56 hooks/
drwxrwxr-x  2 xxx xxx 4096 Nov 24 19:11 info/
drwxrwxr-x 18 xxx xxx 4096 Nov 24 20:29 objects/
drwxrwxr-x  4 xxx xxx 4096 Nov 24 19:11 refs/

If the output is identical to the list above, you have now succesfully created your remote repository.

This is a bare git repository. It is the same as a default repository, but no commits can be made in a bare repository as it does not have a working tree. A working tree is a directory in which all the project files and directories are located.

The next step is to configure the git hooks.

Automating Jekyll deployment with git hooks

Git hooks are scripts that are automatically executed every time a particular event occurs in a git repository. In our case, we want our website to be deployed when a change has been pushed into the website.git repository. To do this, we will create a post-receive hook which clones the bare website.git repository into a temporary directory and deploy our website from there. When the website is deployed, the temporary directory containing the cloned repository can be deleted again.

Create a post-receive hook: vi website.git/hooks/post-receive.

Paste following code:

#!/usr/bin/env bash

GIT_REPO=$HOME/website.git
TMP_GIT_CLONE=/tmp/website
PUBLIC_WWW=$HOME/[WEBSITE_DIR]

git clone -b main $GIT_REPO $TMP_GIT_CLONE
pushd $TMP_GIT_CLONE
bundle exec jekyll build -d $PUBLIC_WWW
popd
rm -rf $TMP_GIT_CLONE

exit

  • [WEBSITE_DIR] - Jekyll’s _site directory.

Creating the local repository:

Next thing you need is a local repository that will be used to modify the website content. To do this you will need to install git on your computer. I’m currently working on Mac OS X Monterey which, by default, comes installed with git. If you do not have git installed yet on your local machine, follow this guide: Install Git on any OS. For the next steps we will use the command line interface (CLI).

Open your CLI. Create a new directory for your local repository:

mkdir website-local

In website-local you will clone the remote repository.

Execute following command:

git clone ssh://[USERNAME]@[SERVER]:[SSH_PORT]/home/[USERNAME]/website.git/ website-local

  • [USERNAME] - The user used to login on the website
  • [SSH_PORT] - SSH port
  • [SERVER] - IP adress or hostname

The next step is to add the remote repository. Do this with:

git remote add origin ssh://[USERNAME]@[SERVER]:[SSH_PORT]/home/[USERNAME]/website.git

Checkout the main branch:

git checkout main

Now copy your Jekyll site into your local repository. A basic Jekyll site typically looks something like this:

├── _config.yml
├── _data
│   └── members.yml
├── _drafts
│   ├── begin-with-the-crazy-ideas.md
│   └── on-simplicity-in-technology.md
├── _includes
│   ├── footer.html
│   └── header.html
├── _layouts
│   ├── default.html
│   └── post.html
├── _posts
│   ├── 2007-10-29-why-every-programmer-should-play-nethack.md
│   └── 2009-04-26-barcamp-boston-4-roundup.md
│   ├── _base.scss
│   └── _layout.scss
└── index.html 

Make sure Jekyll’s _site directory is not copied into your local repository.

Add and commit the files in your local repository:

git commit -am "initial commit" .

Push the changes to your remote repository:

git push origin

If everything is set up correctly, your terminal output should look something like this:

Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 8 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 1.00 KiB | 1.00 MiB/s, done.
Total 4 (delta 3), reused 0 (delta 0), pack-reused 0
remote: Cloning into '/tmp/website'...
remote: done.
remote: /tmp/website ~/website.git
remote: Configuration file: /tmp/website/_config.yml
remote:             Source: /tmp/website
remote:        Destination: [WEBSITE_DIR]
remote:  Incremental build: disabled. Enable with --incremental
remote:       Generating... 
remote:        Jekyll Feed: Generating feed for posts
remote:    GitHub Metadata: No GitHub API authentication could be found. Some fields may be missing or have incorrect data.
remote:                     done in 8.853 seconds.
remote:  Auto-regeneration: disabled. Use --watch to enable.
remote: ~/website.git
To ssh://[SERVER]:[SSH_PORT]/home/[USER]/website.git
   439dd41..55d50e7  main -> main

From line 7 you will see the post-receive hook coming into action. The website.git repository is cloned into /tmp/website. Then the jekyll build is initiated, which deploys the website into [WEBSITE_DIR].

That is it. You have now implemented a CI/CD pipeline for your Jekyll site using a git hook.