I've been using Ghost to blog for some amount of time, but recently I was turned onto Jekyll for blogging with markdown. More to the point, Jekyll is the backend used by Github Pages blogging. We can actually use this and a custom DNS entry to make a free scalable blog, documentation portal or website.
Getting Started
First, let's create a root level repo for our blog. It needs to match our org or user id and end in 'github.io'.
In my case, idjohnson:
You'll also want to set it to public if you want people to be able to see it.
At this point, technically we are done with the MVP. There now exists a very basic GH Pages site:
Themes
Under settings, we can easily pick from some standard themes… Here i chose "Midnight Theme"
and when i saved with a bland README.md, it rebuilt and republished the site:
Custom Domains
What about a custom domain? Maybe something with freshbrewed.science or tpk.pw.
Let's hop over to Azure DNS and add an A Record for tpk.pw:
Here is a CNAME we can use personalblog (personalblog.tpk.pw). Just point that with a CNAME to the existing github.io URL (idjohnson.github.io)
Now let's go back to Github settings for the blog repo:
Let's add the custom DNS entry and save:
we actually see the green published box update with the new URL:
Now sadly this is an HTTP site..
You can go to https but users will get the whole "this cert is bullcrap" warning from google which says, yes it's TLS encrypted, but with an illegit self signed cert (or cert that doesn't match the name): [ note: as we will see in a minute, this is temporary ]
The former URL is still valid
If you wish to enforce SSL (and disable HTTP) then you can enforce that by checking 'enforce https'
I noted that having given Github a beat to verify the DNS (green check), it created an SSL cert for me (thank you Github)
It looks like Github uses LE for that (much as i would do myself):
I certainly did not generate that TLS cert.
We also see that DNS name referenced in two places in our repo.
The first is the _config.yml
title: The TPK Personal Blog
email: isaac.johson@gmail.com
description: >- # this means to ignore newlines until "baseurl:"
Isaac Johnson Personal GH Blog
url: "https://personalblog.tpk.pw" # the base hostname & protocol for your site, e.g. http://example.com
twitter_username: nulubez
github_username: idjohnson
The second is a root level CNAME file:
Updating the blog
say we wish to make a post. How hard is it?
Let's checkout a branch and fire up vs code:
$ ls
CNAME LICENSE README.md _config.yml
$ code .
$ git branch
* main
$ git checkout -b feature/firstpost
Switched to a new branch 'feature/firstpost'
Then we can add the file and push it..
~/Workspaces/idjohnson.github.io$ git add FirstPost.md
~/Workspaces/idjohnson.github.io$ git commit -m "My first post"
[feature/firstpost c246d28] My first post
1 file changed, 19 insertions(+)
create mode 100644 FirstPost.md
~/Workspaces/idjohnson.github.io$ git push
fatal: The current branch feature/firstpost has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin feature/firstpost
~/Workspaces/idjohnson.github.io$ darf
git push --set-upstream origin feature/firstpost [enter/↑/↓/ctrl+c]
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 1.01 KiB | 1.01 MiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
remote:
remote: Create a pull request for 'feature/firstpost' on GitHub by visiting:
remote: https://github.com/idjohnson/idjohnson.github.io/pull/new/feature/firstpost
remote:
To https://github.com/idjohnson/idjohnson.github.io.git
* [new branch] feature/firstpost -> feature/firstpost
Branch 'feature/firstpost' set up to track remote branch 'feature/firstpost' from 'origin'.
Nothing appears on our blog at first. This is because pages is set to a branch spec (in our case "main"):
So let's PR back to main.
We can go to the PR page where we are prompted to make a PR:
Create the PR
Then if there are no conflicts we can merge right away:
which moves it to merged and closed
I realized i did not organize it right.
I moved to a _posts folder and added a header to the markdown:
note: later i realized the missing part was a lack of "---" before line 1 and after line 3.
Then created and merged a PR
Testing locally
Install Jekyll if you have not already: https://jekyllrb.com/docs/installation/
In fact, getting started with Jekyll locally is as simple as a few steps (documented here)
$ gem install jekyll bundler
# Create a new Jekyll site at ./blog.
$ jekyll new blog
# cd to blog and build
$ cd myblog && bundle exec jekyll serve
# now serving on localhost:4000
We will be looking at the completed repo which is made public here:
https://github.com/idjohnson/idjohnson.github.io
You'll want to use `bundle install` and `bundle update` when updating content
~/Workspaces/idjohnson.github.io$ bundle update
Fetching gem metadata from https://rubygems.org/...........
Resolving dependencies...
Using concurrent-ruby 1.1.9
Using minitest 5.14.4
Using thread_safe 0.3.6
Using zeitwerk 2.4.2
Using public_suffix 4.0.6
Using bundler 2.2.26
Using unf_ext 0.0.7.7
Using eventmachine 1.2.7
Using ffi 1.15.3
Using faraday-em_http 1.0.0
Using faraday-excon 1.1.0
Using faraday-httpclient 1.0.1
Using faraday-net_http_persistent 1.2.0
Using faraday-patron 1.0.0
Using faraday-rack 1.0.0
Using multipart-post 2.1.1
Using forwardable-extended 2.6.0
Using gemoji 3.0.1
Using rb-fsevent 0.11.0
Using rexml 3.2.5
Using liquid 4.0.3
Using mercenary 0.3.6
Using rouge 3.26.0
Using safe_yaml 1.0.5
Using racc 1.5.2
Using jekyll-paginate 1.1.0
Using rubyzip 2.3.2
Using jekyll-swiss 1.0.0
Using unicode-display_width 1.7.0
Using i18n 0.9.5
Using unf 0.1.4
Using tzinfo 1.2.9
Using ethon 0.14.0
Using rb-inotify 0.10.1
Using addressable 2.8.0
Using pathutil 0.16.2
Using kramdown 2.3.1
Using nokogiri 1.12.3 (x86_64-linux)
Using ruby-enum 0.9.0
Using faraday-em_synchrony 1.0.0
Using terminal-table 1.8.0
Using simpleidn 0.2.1
Using typhoeus 1.4.0
Using sass-listen 4.0.0
Using listen 3.7.0
Using kramdown-parser-gfm 1.1.0
Using commonmarker 0.17.13
Using dnsruby 1.61.7
Using sass 3.7.4
Using jekyll-watch 2.2.1
Using jekyll-sass-converter 1.5.2
Using execjs 2.8.1
Using faraday-net_http 1.0.1
Using activesupport 6.0.4.1
Using colorator 1.1.0
Using html-pipeline 2.14.0
Using http_parser.rb 0.6.0
Using ruby2_keywords 0.0.5
Using em-websocket 0.5.2
Using coffee-script-source 1.11.1
Using jekyll 3.9.0
Using coffee-script 2.4.1
Using jekyll-avatar 0.7.0
Using jekyll-redirect-from 0.16.0
Using jekyll-relative-links 0.6.1
Using jekyll-remote-theme 0.4.3
Using jekyll-seo-tag 2.7.1
Using jekyll-sitemap 1.4.0
Using jekyll-titles-from-headings 0.5.3
Using jemoji 0.12.0
Using jekyll-coffeescript 1.1.1
Using jekyll-theme-architect 0.2.0
Using jekyll-theme-cayman 0.2.0
Using jekyll-theme-dinky 0.2.0
Using jekyll-theme-hacker 0.2.0
Using jekyll-theme-leap-day 0.2.0
Using jekyll-theme-merlot 0.2.0
Using jekyll-theme-midnight 0.2.0
Using jekyll-theme-minimal 0.2.0
Using jekyll-theme-modernist 0.2.0
Using jekyll-theme-slate 0.2.0
Using jekyll-theme-tactile 0.2.0
Using jekyll-theme-time-machine 0.2.0
Using faraday 1.7.0
Using jekyll-commonmark 1.3.1
Using jekyll-default-layout 0.1.4
Using jekyll-commonmark-ghpages 0.1.6
Using jekyll-mentions 1.6.0
Using jekyll-optional-front-matter 0.3.2
Using jekyll-readme-index 0.3.0
Using sawyer 0.8.2
Using jekyll-feed 0.15.1
Using octokit 4.21.0
Using minima 2.5.1
Using jekyll-gist 1.5.0
Using jekyll-github-metadata 2.13.0
Fetching github-pages-health-check 1.17.7 (was 1.17.2)
Using jekyll-theme-primer 0.6.0
Installing github-pages-health-check 1.17.7 (was 1.17.2)
Fetching github-pages 219 (was 218)
Installing github-pages 219 (was 218)
Bundle updated!
~/Workspaces/idjohnson.github.io$
Next to serve this locally, you can use `bundle exec jekyll serve`
~/Workspaces/idjohnson.github.io$ bundle exec jekyll serve
Configuration file: /home/builder/Workspaces/idjohnson.github.io/_config.yml
Source: /home/builder/Workspaces/idjohnson.github.io
Destination: /home/builder/Workspaces/idjohnson.github.io/_site
Incremental build: disabled. Enable with --incremental
Generating...
Remote Theme: Using theme pages-themes/midnight
Jekyll Feed: Generating feed for posts
GitHub Metadata: No GitHub API authentication could be found. Some fields may be missing or have incorrect data.
done in 1.619 seconds.
/home/builder/gems/gems/pathutil-0.16.2/lib/pathutil.rb:502: warning: Using the last argument as keyword parameters is deprecated
Auto-regeneration may not work on some Windows versions.
Please see: https://github.com/Microsoft/BashOnWindows/issues/216
If it does not work, please upgrade Bash on Windows or run Jekyll with --no-watch.
Auto-regeneration: enabled for '/home/builder/Workspaces/idjohnson.github.io'
Server address: http://127.0.0.1:4000
Server running... press ctrl-c to stop.
[2021-08-25 13:52:31] ERROR `/favicon.ico' not found.
The key things you'll note are that posts are automatically added from _posts directories. Note: the underscore is important there.
And we can see that matches here:
https://github.com/idjohnson/idjohnson.github.io/blob/feature/firstpost/_posts/2021-08-23-firstpost.md
The other important note is that the markdown really must be prefixed with a date string. Also, in the markdown itself, we need a YAML block at the top *WITH* delimiters. Categories are basically tags.
---
layout: post
title: "First Post"
date: 2021-08-23 11:10:43 -0500
categories: personal
---
## First Post
So now we have a blog. What's next? What content do you want to share?
…
The other thing you'll note is that we can serve pages from more than one folder.
Here i have a "/docs" root with a post: https://github.com/idjohnson/idjohnson.github.io/blob/feature/firstpost/docs/_posts/2021-08-23-test2.md which is served just fine.
and of course the other two in a _posts folder:
https://github.com/idjohnson/idjohnson.github.io/tree/feature/firstpost/_posts
Adding pages
Say that I wanted to add a page.
First, it's important that *if* you move off the default theme, you create _layouts for any top level type. E.g. page:
Then we can create a page. Here i will add it directly under docs:
and that permalink is now served as http://localhost:4000/wassup/
We can now add and push it to Github:
`~/Workspaces/idjohnson.github.io$ git status
On branch feature/firstpost
Your branch is up to date with 'origin/feature/firstpost'.
Untracked files:
(use "git add <file>..." to include in what will be committed)
docs/sample.md
nothing added to commit but untracked files present (use "git add" to track)
~/Workspaces/idjohnson.github.io$ git add docs/
~/Workspaces/idjohnson.github.io$ git commit -m "add a wassup sample page"
[feature/firstpost 0f639e0] add a wassup sample page
1 file changed, 6 insertions(+)
create mode 100644 docs/sample.md
~/Workspaces/idjohnson.github.io$ git push
Enumerating objects: 6, done.
Counting objects: 100% (6/6), done.
Delta compression using up to 8 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 435 bytes | 435.00 KiB/s, done.
Total 4 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To https://github.com/idjohnson/idjohnson.github.io.git
b455bdd..0f639e0 feature/firstpost -> feature/firstpost
However, when i look, i of course don't see it live;
This is because we don't serve out of the feature/firstpost branch.. we need to merge to main to make it live.
We just need to make a PR:
Then merge it to make it live
completed
after a minute or so (hey, it's free) we see the pipeline ran and our blog is rendered and live
This includes pages
As an aside, on some browsers i needed to add 'index.html' to the path.
Cleaning up.
So now that we have a sample blog. having a bunch of "hello world" posts won't be of much use.
I'll first create a new branch off main (now that its merged, it should have more content)
~/Workspaces/idjohnson.github.io$ git checkout main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
~/Workspaces/idjohnson.github.io$ git pull
remote: Enumerating objects: 2, done.
remote: Counting objects: 100% (2/2), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 2 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (2/2), 1.20 KiB | 1.20 MiB/s, done.
From https://github.com/idjohnson/idjohnson.github.io
fa5fb09..0365008 main -> origin/main
Updating fa5fb09..0365008
Fast-forward
.gitignore | 5 ++
404.html | 25 ++++++
Gemfile | 32 ++++++++
Gemfile.lock | 282 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
_config.yml | 55 ++++++++++++-
_layouts/home.html | 4 +
_layouts/page.html | 4 +
_layouts/post.html | 4 +
FirstPost.md => _posts/2021-08-23-firstpost.md | 9 ++-
_posts/2021-08-23-welcome-to-jekyll.markdown | 29 +++++++
about.markdown | 18 +++++
docs/_posts/2021-08-23-test2.md | 7 ++
docs/sample.md | 6 ++
index.markdown | 15 ++++
14 files changed, 493 insertions(+), 2 deletions(-)
create mode 100644 .gitignore
create mode 100644 404.html
create mode 100644 Gemfile
create mode 100644 Gemfile.lock
create mode 100644 _layouts/home.html
create mode 100644 _layouts/page.html
create mode 100644 _layouts/post.html
rename FirstPost.md => _posts/2021-08-23-firstpost.md (92%)
create mode 100644 _posts/2021-08-23-welcome-to-jekyll.markdown
create mode 100644 about.markdown
create mode 100644 docs/_posts/2021-08-23-test2.md
create mode 100644 docs/sample.md
create mode 100644 index.markdown
~/Workspaces/idjohnson.github.io$ git checkout -b feature/cleanup-blog
Switched to a new branch 'feature/cleanup-blog'
First, in /index.markdown let's create an IF block for a new field we will use called 'visible'
---
# Feel free to add content and custom Front Matter to this file.
# To modify the layout, see https://jekyllrb.com/docs/themes/#overriding-theme-defaults
layout: home
---
My posts:
<ul>
{% for post in site.posts %}
{% if post.visible== 1 %}
<li>
<a href="{{ post.url }}">{{ post.title }}</a>
{{ post.excerpt }}
</li>
{% endif %}
{% endfor %}
</ul>
Next, in our posts, set the firstpost to visible:
_posts/2021-08-23-firstpost.md:
---
layout: post
title: "First Post"
date: 2021-08-23 11:10:43 -0500
categories: personal
visible: 1
---
## First Post
and the rest use "visible: 0"
now serve locally:
But say you want to avoid even that. You could add a line in the post.html to also mask it (though note the variable is under "page" not "post")
---
layout: default
---
{% if page.visible==1 %}
{{ content }}
{% endif %}
For instance, maybe i want to just highly draft posts are just that - drafts.
---
layout: default
---
{% if page.visible!=1 %}
<h1><font color=red>**DRAFT POST: MAY BE INCOMPLETE**</font></h1>
{% endif %}
{{ content }}
and those marked visible: 0 will show that
Removing the GH link
I found a few guides, but the solution was close but not in any of them.
You'll want to create an assets/css/style.scss which adds to the existing style sheet.
Then you can set the header to visibility of none:
assets/css/style.scss:
---
---
@import "{{ site.theme }}";
#header {
display: none !important;
}
.btn {
display: none !important;
}
I then pushed, created a PR and completed it to make it live:
Summary
In this article we started by creating a new Github repo with our username/org and then setting up a basic blog with Github pages. We then setup Jekyll with a new theme and showed how to post blog posts and pages.
We then added a personal DNS entry and explored local testing. Lastly we updated our templates to hide future/deprecated posts with a visibility flag and hide the "view on Github" link header for pages that might not need it.