How I Built This Blog: Jekyll + GitHub Pages for Free

No database, no server bills, no CMS lock-in. Here's exactly how I built and deployed this blog with Jekyll and GitHub Pages — and how you can too.

This blog you’re reading right now costs me exactly $0 per month to run. No hosting bill, no database, no CMS subscription. It’s a static site built with Jekyll and hosted on GitHub Pages. Here’s the full recipe.

Why Jekyll?

Jekyll is a static site generator: you write posts in Markdown, and it turns them into plain HTML files. For a personal blog, that means:

  • Free hosting — GitHub Pages serves static sites for free, straight from your repo.
  • Blazing fast — there’s no database or server-side rendering. Just files.
  • Version controlled — every post is a commit. Full history, branches, pull requests for your own blog.
  • Markdown-native — if you can write a README, you can write a blog post.

Step 1: Create the repository

GitHub gives every user one special repository: <username>.github.io. Push a site there and it goes live at https://<username>.github.io. Create it, then clone it locally:

git clone https://github.com/<username>/<username>.github.io.git
cd <username>.github.io

Step 2: Set up Jekyll

You’ll need Ruby. On most systems:

# macOS (with Homebrew)
brew install ruby

# Ubuntu / Debian
sudo apt install ruby-full build-essential

# Then install Jekyll
gem install jekyll bundler

Create a Gemfile in your repo root so builds are reproducible:

source "https://rubygems.org"

gem "jekyll", "~> 4.3.0"
gem "jekyll-feed", "~> 0.17"      # RSS feed
gem "jekyll-seo-tag", "~> 2.8"    # meta tags
gem "jekyll-sitemap", "~> 1.4"    # sitemap.xml
gem "jekyll-paginate", "~> 1.1"   # homepage pagination
gem "webrick", "~> 1.8"           # `jekyll serve` on Ruby 3+

Step 3: Add the config

Jekyll is configured with a single YAML file, _config.yml:

title: My Awesome Blog
description: Thoughts on code and other things.
url: https://<username>.github.io

permalink: pretty
markdown: kramdown

plugins:
  - jekyll-feed
  - jekyll-seo-tag
  - jekyll-sitemap
  - jekyll-paginate

paginate: 5
paginate_path: /page:num/

Step 4: Write your first post

Posts live in _posts/ and follow a strict naming convention: YEAR-MONTH-DAY-title.md. Each one starts with front matter — a YAML block that Jekyll reads for metadata:

---
layout: post
title: "My First Post"
date: 2026-09-10 09:00:00 +0000
tags: [hello, blogging]
---

Hello, world! This is my first post.

## A section heading

Some text with `inline code` and a [link](https://example.com).

The {{ site.title }} style double-curly syntax you see in Jekyll themes is Liquid, the templating language. Layouts in _layouts/ and reusable snippets in _includes/ let you build pages from components:

{% for post in site.posts limit: 5 %}
  <a href="{{ post.url }}">{{ post.title }}</a>
{% endfor %}

Inside your own posts, wrap any Liquid-looking code in raw tags ({&#37; raw &#37;} … {&#37; endraw &#37;}), otherwise Jekyll will try to render it. Ask me how I know.

Step 5: Preview locally

bundle install
bundle exec jekyll serve

Open http://localhost:4000 and there’s your blog, with live rebuilds on every save.

Step 6: Deploy with GitHub Actions

The modern way to publish is a workflow file at .github/workflows/jekyll.yml that builds your site and deploys it to Pages on every push to main:

name: Deploy Jekyll site to GitHub Pages

on:
  push:
    branches: [main]

permissions:
  contents: read
  pages: write
  id-token: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: "3.3"
          bundler-cache: true
      - uses: actions/configure-pages@v5
      - run: bundle exec jekyll build
        env:
          JEKYLL_ENV: production
      - uses: actions/upload-pages-artifact@v3

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - id: deployment
        uses: actions/deploy-pages@v4

Then go to your repo’s Settings → Pages and set Source to GitHub Actions. Push to main, wait a minute, and your blog is live. Every future post is just git push away.

What I added on top

The basics above get you a working blog in an afternoon. This site adds a few extras, all in the repo if you want to steal ideas:

Feature How
Custom theme, dark mode Hand-written CSS + a tiny JS toggle
Client-side search A generated search.json + ~60 lines of vanilla JS
Reading progress + TOC ~40 lines of vanilla JS, no libraries
Comments (optional) Giscus, backed by GitHub Discussions
SEO + social cards jekyll-seo-tag does the heavy lifting

Final thoughts

Could I have used Next.js, Astro, or Hugo? Sure — they’re all great. But for a personal blog, Jekyll hits a sweet spot: dead simple, free forever, and boring in the best possible way. The less time I spend on infrastructure, the more time I spend writing.

Now stop reading about blogging platforms and go write something. The best blog setup is the one that’s already published.