Logo
Logo

Programming by Stealth

A blog and podcast series by Bart Busschots & Allison Sheridan.

PBS 177 of X: Publishing A Basic Jekyll Site (GitHub Pages)

01 Mar 2025

In the previous instalment we learned how to use GitHub Actions to publish a web app using a custom build system, Webpack to be precise. We’re now moving on to solving a completely different problem — using GitHub Pages with Jekyll as a content management system. This is GitHub Pages’ default mode of operation, so we won’t need to build our own custom GitHub Actions this time, instead, we’ll be relying on the default GitHub Pages workflow provided by GitHub.

In the previous instalment, we made a point of using GitHub Pages in such a way that we could test our apps locally before pushing the tested code to GitHub and triggering an automated deployment to the internet. This kind of local testing removed a lot of friction from the development process, greatly speeding up debugging in particular. Hence, we’ll focus on using GitHub Pages and Jekyll in such a way as to facilitate the same kind of local testing. This approach complicates the initial setup of our projects a little, but that extra work up front more than pays for itself over the lifetime of any site developed in this way!

Finally, rather than building the theme for our sites completely from scratch, we’ll use Bootstrap as our scaffold, and build on top of the solid foundation it provides. Because Jekyll supports Sass (Syntactically Awesome Style Sheets) out of the box, and because Bootstrap uses Sass for customisation, this is the perfect opportunity to learn how to customise Bootstrap. Bootstrap was built to be both configurable and extendable, so its default behaviour is just a taste of its full capabilities.

In this instalment we’re going to start by laying a solid foundation for our work with Jekyll. We’ll learn how to start a Jekyll project on our local PCs, test and run it, and then publish it to the internet with GitHub Pages. We’ll start our Jekyll project with the simplest possible HTML 5 theme, and then add Bootstrap in such a way that it is ready to be customised later in the series.

Matching Podcast Episode

You can also Download the MP3

Read an unedited, auto-generated transcript with chapter marks: PBS_2025_03_01

Instalment Resources

Some Important Context

It’s important to note that if you don’t want the ability to test your GitHub Pages site locally, you can skip much of this instalment and simply enable GitHub Pages to start serving your docs folder just like we did in the first of the two worked examples in the previous instalment. At that point you can create a standard Jekyll directory structure and just start building your site.

However, there’s a very good reason not to take that easy route if you plan to use Jekyll as a full content management system — if you don’t follow these steps, your only means of testing your site will be to actually publish it to the internet by doing a full commit to main for each and every change you want to test. This is inordinately tedious for debugging work!

The extra work described in this instalment results in a Jekyll site that is both compatible with GitHub Pages, and with a local install of Jekyll.

I mentioned in the introductory instalment that GitHub Pages uses a customised version of Jekyll, and that’s a vitally important point to understand. Because if it’s various customisations, you don’t install Jekyll directly in your docs folder, instead you install a github-pages package which lists many dependencies, including the specific version of Jekyll currently supported by GitHub Pages. This is always a somewhat older Jekyll version. As of March 2025, the main Jekyll project is at version 4.3, but GitHub Pages is using Jekyll version 3.10.

This lagging version number is important to keep in mind when reading the Jekyll documentation. Newer features will be annotated with the Jekyll version in which they were introduced, so at any time, some of the newer features described in the documentation will not yet be available for use with GitHub Pages.

Preparing to Run Jekyll Locally

Note: the instructions in this section need to be run on any machine where you have not used Jekyll before, even if you are cloning an existing Jekyll project from Git rather than starting a fresh one.

To test a Jekyll site locally you need to install Jekyll on your computer. Jekyll is written in Ruby, so the first thing you’ll need to do is install Ruby 3.

Warning: before installing Ruby 3 make sure you have the latest version of the Xcode developer tools installed and that the license has been accepted. You don’t need the full Xcode, you just need the Command Line Utilities. Look for the latest non-beta version available on this page developer.apple.com/…. If your tools are not up to date and the license accepted, strange errors can occur when installing various dependencies later in the process. If the developer tools are not fully in order at the point in time you install Ruby 3, simply fixing the developer tools later will not resolve the issues installing the dependencies, at that point, the only solution will be to completely remove Ruby 3 and start over. The command xcode-select --install should trigger their installation or update.

Macs ship with Ruby version 2 installed, but it’s easy to upgrade to Ruby 3 using Homebrew.

To stop random things breaking on your Mac, it’s important not to replace the built-in Ruby, but to augment it with our desired newer version and a tool for flipping your terminal between versions as needed. We’ll be following Jekyll’s advice and using chruby as our Ruby version manager.

If you don’t already have it installed, start by installing Homebrew using the instructions on their home page.

With Homebrew installed we can now install the universal Ruby version installer and the Chruby version switcher with the command:

brew install chruby ruby-install

Next, install the version of Ruby currently listed on the GitHub Pages dependencies page. As of March 2025 the command to run is:

ruby-install ruby 3.3.4

This will trigger a Unix/Linux style installation from source code, so you’ll see lots of output scrolling by, and it’ll take some time.

When Ruby finishes installing, the next step is to configure your shell to automatically use Chruby with the following commands (replacing the version number with the one you just installed):

echo "source $(brew --prefix)/opt/chruby/share/chruby/chruby.sh" >> ~/.zshrc
echo "source $(brew --prefix)/opt/chruby/share/chruby/auto.sh" >> ~/.zshrc
echo "chruby ruby-3.3.4" >> ~/.zshrc # run 'chruby' to see actual version

If you suspect you may have installed Ruby before, it is important to check that your ~/.zshrc file does not container older duplicates of the three directives we just added. You can view the contents of the file with the command cat ~/.zshrc. If there are older versions, edit the file with your favourite text editor and keep the copies nearest the end of the file, those are the ones we just appended by the above commands.

These instructions assume a modern version of macOS using Zsh, if you’re still using Bash edit the commands to append to ~/.bash_profile instead of ~/.zshrc!

With all that done, close and reopen your Terminal to pick up the new Ruby configuration. Verify you’re using Ruby 3 now with the command:

ruby --version

We’re going to be installing Ruby’s equivalent of JavaScript packages, but I just want to note that Ruby doesn’t call them ‘packages’, it calls them ‘gems’, which is some fun wordplay on the language’s name 🙂

To easily install Jekyll and its dependencies, we need to install the optional Ruby package manager Bundler. We can do that with the command:

gem install bundler

Now that we have bundler installed, we have everything we need to start running Jekyll locally.

Worked Example — A Basic Jekyll Site with Customisable Bootstrap

We’re now ready to build our first site, and we’re going to do it in such a way that we can test it locally.

Step 1 — Create a New Repository on GitHub

Log in to GitHub and create a new repository. Be sure to choose the following settings:

  1. Select public from the visibility radio group (GitHub Pages can be used on private repositories, but not for free).
  2. Not needed, but I strongly recommend ticking the box to Initialise the repo with a README file so you have a place to add instructions to yourself related to the site. This will also initialise the repository, making it easier to start using.
  3. Choose Jekyll in the Add .gitignore dropdown.

Step 2 — Clone Your Repo & Configure GitHub Pages Locally

Clone your newly created repo and open a command prompt in that folder.

Because Jekyll is a Ruby app, we are going to manage it using Ruby’s equivalent of an NPM configuration file, a file named Gemfile. This file will declare the specific versions we need for each Ruby package that needs to be installed.

Start by creating a docs folder, then change into it with the commands:

mkdir docs; cd docs

Rather than managing our Gemfile directly, we’re going to let the Bundler do that for us by using the bundle command.

Before we can start adding our desired gems we need we need to initialise a blank Gemfile with the command:

bundle init

You’ll see that running this command in the docs folder created the file docs/Gemfile.

Older versions of Ruby contained the package webrick as a standard package, but Ruby 3 doesn’t, so to avoid errors, let’s first add it to our Gemfile with the command:

bundle add webrick

This command both adds the gem to our Gemfile, and, if needed, installs it on our system.

Next, we want to add the GitHub Pages gem. This will install the needed version of Jekyll as a dependency. We can do this with the command:

bundle add github-pages --group jekyll_plugins

You’ll see that this command adds many dependencies. If you scroll up you can see the exact version of Jekyll that was added, in February 2025 the line is:

Fetching jekyll 3.10.0

Step 3 — Create an Initial Skeleton Site

Before we can test everything is working, we need to create a very basic Jekyll site in the docs folder.

First, we’ll create a very basic settings file for the site by creating a file named docs/_config.yml with the following contents (you’ll find a copy of this file in the Instalment Resources as _config-1.yml which you’ll need to rename to _config.yml):

# Site Settings
title: PBS 177 Test Site

Finally, create a very basic home page by creating a file named docs/index.md with the following content (included in the resources as index-1.md which you would rename to index.md):

---
title: Home
---
# Hello World!

This is a really basic Jekyll site 🙂

Step 4 — Test Jekyll Locally

We now have Jekyll configured and a basic site in place so we’re ready to test it with the command:

bundle exec jekyll serve

This will render the contents of the docs directory as a website and write the generated files to the folder docs/_site and then start a locally running web server on port 4000 with that folder as the source. You’ll see the URL for this local server in the output, and by default it will be http://127.0.0.1:4000.

Note that this command does not exit, but keeps running. You can kill the local server at any time with ctrl+c or closing the common prompt, but you’ll only need to do that when you’re finished for the day, because the Jekyll server monitors the file system for changes, and automatically rebuilds your site each time you change a file in the the docs folder. This makes debugging very easy.

To see the generated site, open the URL in your browser.

Notice that the site title we defined in _config.yml appears in the two places in the default theme:

  1. In the browser window/tab title
  2. In the automatically generated navigation at the top of the page as the link to always take you to the site’s home page

Notice that the page title defined in the front matter in index.md also appears in the browser window/tab title.

The rest of the content of the home page is the rendered markdown from the body of index.md.

Step 5 — Commit & Push to GitHub

Before we commit our changes, we need to add docs/Gemfile.lock to the .gitignore file. You can do that by editing the .gitignore file manually, by using your Git GUI app of choice, or by running the following command:

echo '/docs/Gemfile.lock' >> ../.gitignore

Note that if you forgot to tell GitHub to use the standard ignore file for Jekyll you’ll also need to add the docs/_site folder to your ignore file.

You can now commit all remaining new and changed files, and push your commit to GitHub.

Step 6 — Enable GitHub Pages & Test

Log in to the GitHub portal, open your repository, and then navigate to the GitHub Pages settings page (SettingsPages).

Enable GitHub Pages with the following settings:

  1. Leave the Source as Deploy from a branch
  2. Set the Branch to main with the folder /docs and save

Navigate to Actions from the top menu bar and watch the site build.

When the build completes, click on the workflow name to see the details, this will include the published URL of your site.

Open the site in your browser to verify it works!

Step 7 — Add Bootstrap

The version of Bootstrap we’ve used to date has been the compiled version, that is to say, a collection of finished CSS files. If you open those files you’ll find they’re massively repetitive, with very similar code repeated many times for similarly styled items. For example, the styles for all classes with -success in their name share the same line for setting the colour. It would be nearly impossible to actually build a CSS library as large as Bootstrap by hand. That’s why it’s not written by hand, instead, it’s written in Sass. Sass is a language I like to think of as CSS with superpowers because it supports things like nesting, complex variables, and looping. Sass files get compiled down to CSS files, which is why Sass is referred to as a CSS pre-processor. Simple Sass files can be used to generate complex CSS files.

To effectively customise Bootstrap you need to use the source Sass files as your starting point. You then make your additions and changes in Sass, and render your custom version of Bootstrap to CSS.

Due to the fact that the default Jekyll theme uses CSS class names that clash with Bootstrap’s CSS class names, we can’t just add Bootstrap to the default theme. Instead, we’re going to need to create our own theme. Don’t worry, this is not difficult to do.

Enable Advanced Sass Processing on our Site

Jekyll has native Sass support, so it has almost everything we need to compile the Bootstrap Sass files to CSS for us. Almost, but not quite! It’s just missing one important feature — a so-called autoprefixer for automatically handling browser-specific quirks through vendor prefixes when compiling Sass to CSS. Thankfully there’s a Jekyll plugin available to provide this functionality, it’s just not installed by default.

Before we can configure the plugin we need to install it locally with the following command from /docs:

bundle add jekyll-autoprefixer --group jekyll_plugins

As of March 2025, there’s a known issue with this plugin that requires a specific version of the execjs gem to be used, so add that too with the command:

bundle add execjs -v 2.7

Then we can enable and configure the plugin in our site settings. Update docs/_config.yml to the following (in the resources as _config-2.yml):

# Site Settings
title: PBS 177 Test Site

# Build Settings
plugins:
  - jekyll-autoprefixer

# Settings for the autoprefixer plugin
autoprefixer:
  browsers:
    - last 4 versions
    - Safari > 2

Add the Needed Bootstrap Files

We’re now ready to download the source version of Bootstrap and add it to our site.

On the Bootstrap download page, click the button to Download source and extract the resulting ZIP file.

Before we add the Bootstrap code, let’s take a moment to learn about two important folder locations used by Jekyll that relate to JavaScript, CSS & Sass files.

  1. By convention, and as per the examples in the Jekyll documentation, the assets folder is used to store files referenced in your theme like Javascript files, cascading style sheets (CSS), and images like icons & logos. Another convention, though not quite as universal, is to organise the assets folder into type-specific subfolders. We’ll be following this convention by using:
    1. assets/js for our JavaScript files.
    2. assets/css for our generated CSS. But, note that we’ll be writing our CSS as Sass, so the file extension on the files we create will be .scss. Jekyll will convert them to plain CSS when it builds the site, so the files that appear in the generated website will have their file extensions changed from .scss to .css.
  2. The _sass folder is used to store additional Sass imports, this is to say Sass helper files referenced in assets/css/*.scss files using the @import Sass directive.

I want to draw attention to two very important things to understand about using Jekyll to convert SASS to CSS:

  1. Assets with the .scss file extension that contain front matter (usually blank) are compiled to CSS automatically. This means that a file named assets/css/style.scss with front matter will get converted to assets/css/style.css, and your theme’s HTML needs to reference the file with that .css file extension for it to be successfully used. But, if you forget to add front matter, the Sass conversion will not happen!
  2. The entry points to your Sass code must be assets, so sticking to best practices, that means you add your primary Sass file(s) to assets/css. Within those primary Sass files, each time you call @import, Sass will try to find the requested Sass utility code in the _sass folder.

Putting it all together, we need to create a primary Sass file in docs/assets/css, save Bootstrap’s Sass files in docs/_sass, and Bootstrap’s JavaScript files in docs/assets/js.

In your local clone of your repository:

  1. Create a folder named docs/_sass/bootstrap and copy the entire contents of the scss folder from the downloaded and extracted Bootstrap source ZIP here.
  2. Create a folder named docs/assets/js and copy both the dist/js/bootstrap.bundle.min.js and dist/js/bootstrap.bundle.min.js.map files from the Bootstrap source ZIP here.

Because Bootstrap’s Sass code contains a folder named vendor which often needs to be ignored in Jekyll sites, it is included in GitHub’s ignore template for Jekyll which we chose to use when creating our repo. This means we need to add an exception for just Bootstrap’s vendor folder to our .gitignore. As described in instalment PBS 120 in our long Git series, exceptions to being ignored also get defined in the .gitignore file, but they need to come after the lines that would otherwise cause them to be ignored. Exceptions are pre-fixed with an (!). So, all we need to do is add the following to the end of our ignore file:

!/docs/_sass/bootstrap/vendor/

You can do that by editing the .gitignore file manually, by using your Git GUI app of choice, or by running the following command:

echo '!/docs/_sass/bootstrap/vendor/' >> ../.gitignore

We’re now ready to create a Sass entry point that imports Bootstrap, and, for now, does nothing more. Create a file named docs/assets/css/style.scss with the following contents (assets-css-style-1.scss in the resources):

---
---
@import "bootstrap/bootstrap";

At this point, if we asked Jekyll to rebuild our site, all of Bootstrap would appear in our generated site as assets/css/style.css.

Create our Very Basic Custom Theme

Finally, we need to create the simplest possible custom theme and include the Bootstrap CSS in it.

Create a file named docs/_layouts/default.html with the following content (in the resources as _layouts-default-1.html):

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>{{ page.title }} | {{ site.title }}</title>
  <link rel="stylesheet" href="{{ '/assets/css/style.css' | relative_url }}">
</head>
<body>
<main class="container pt-3">
    {{ content }}
</main>
<script src="{{ '/assets/js/bootstrap.bundle.min.js' | relative_url }}"></script>
</body>
</html>

As you can see, this file contains mostly just regular HTML, but it does contain a few Liquid template strings (the sections wrapped in doubled curly braces, i.e. {{ … }}). For now, I’m going to ask you to accept this syntax as magic sauce that makes the theme work, but we will learn all about Liquid templates as the series progresses.

Verify the Changes

With these changes made, we can test our site locally with the bundle exec jekyll serve command, and you’ll see it now looks like a regular Bootstrap page.

Commit all changed files and push to GitHub. Watch the build action as normal, and then verify that the published site is now successfully using our basic Bootstrap theme.

Step 8 — Test Bootstrap

The fact that the regular Bootstrap CSS has been successfully generated and included in the template can be inferred from the fact that the page is rendering in Bootstrap’s default sans-serif font, but that doesn’t prove that we have successfully achieved the ability to customise Bootstrap, or, that Bootstrap’s JavaScript has been successfully incorporated into our theme. Let’s demonstrate both of those things in turn by making some temporary edits to our base theme.

Verifying Bootstrap Customisability

One of the mechanisms by which Bootstrap can be customised is by overriding the value of one of its Sass variables. As an example, let’s update the variables that control the body background and body text colours. Open docs/assets/css/style.scss and edit it to add two variable definitions before the line that imports Bootstrap. The updated file should look like the following (assets-css-style-2.scss in the resources):

---
---
/* Override Bootstrap Variables */
$body-bg: LightSkyBlue;
$body-color: MidnightBlue;

/* Import Bootstrap */
@import "bootstrap/bootstrap";

Saving this file and regenerating your site locally will now show dark blue text on a light blue background.

Verify Bootstrap JavaScript

To verify that Bootstrap’s JavaScript is being correctly loaded by the theme, we’ll add a dismissible Bootstrap alert to the top of our theme. Edit docs/_layouts/default.html to add the following alert at the top of the <main> tag:

<div class="alert alert-info alert-dismissible fade show" role="alert">
  Click my close button to prove that Bootstrap JavaScript is working!
  <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>

The complete file should now contain (_layouts-default-2.html in the resources):

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>{{ page.title }} | {{ site.title }}</title>
  <link rel="stylesheet" href="{{ '/assets/css/style.css' | relative_url }}">
</head>
<body>
<main class="container pt-3">
    <div class="alert alert-info alert-dismissible fade show" role="alert">
        Click my close button to prove that Bootstrap JavaScript is working!
        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
    </div>
    {{ content }}
</main>
<script src="{{ '/assets/js/bootstrap.bundle.min.js' | relative_url }}"></script>
</body>
</html>

Rebuild your site locally, then open it in your browser. You should see an alert with a close button. Clicking the close button should dismiss the alert.

A Final GitHub Pages Check

As a final sanity check, commit these changes and push them to GitHub. Wait for the site to rebuild, and then verify that the colour change and alert dismissal are behaving the same in GitHub Pages as they are locally.

Final Thoughts

This instalment was about building a scaffolding to get us started. We’ve left a lot of proverbial mystery meat, which always makes me a little uncomfortable. But, we can now get a basic a basic Jekyll site with a custom theme using customisable Bootstrap up and running both locally and on GitHub Pages. This gives us the ability to develop our site off-line, and to publish updates to the internet as and when we think our work is in an appropriate state to do so.

We’re now ready to start adding our content and evolving our theme. The next few instalments are going to be a little bit challenging because we’ll need to use a little bit of many new technologies all at once. Inevitably there’ll need to be a little hand-waving about a few details initially, but, rest assured, all that detail will be filled in before we finish the series!

Join the Community

Find us in the PBS channel on the Podfeet Slack.

Podfeet Slack