2015 update — The latest GitHub Pages stack supports some widely-used plugins, so you might not need to use the workflow described below. Post has been updated to use Jekyll 2.
GitHub Pages basically runs your project's gh-pages
branch through jekyll build --safe
. "Unfortunately", the --safe
flag prevents from loading any plugins, when many people would want to use some not supported by GitHub Pages. There is no way to bypass this flag, but there is a workaround.
The idea is quite straightforward:
jekyll
command locally (allowing for using any plugins)gh-pages
branch ready for deploymentgh-pages
branchThis workflow is well known. Now, the challenging point might be #2: generating the cleanest gh-pages
branch possible. It's by no means mandatory, but a nice optimization to perform when generating the website locally. Kind of a challenge if you will!
gh-pages
branchSay you have the following brand-new Jekyll project:
$ jekyll new mywebsite
New jekyll site installed in /home/user/mywebsite/
$ cd mywebsite
$ ls
about.md _config.yml css feed.xml _includes index.html _layouts _posts _sass
$ jekyll new mywebsite
New jekyll site installed in /home/user/mywebsite/
$ cd mywebsite
$ ls
about.md _config.yml css feed.xml _includes index.html _layouts _posts _sass
Running jekyll build
will generate your static content into a _site/
folder (default configuration).
Once this project is tracked with git, one could simply create a gh-pages
branched off of master
. It could be nice though to have the gh-pages
branch contain only the latest version of the website as available in _site/
, without any git history or non-production content.
The goal is to have gh-pages
contain:
# While on gh-pages:
$ tree .
.
├── about
│ └── index.html
├── css
│ └── main.css
├── feed.xml
├── index.html
└── jekyll
└── update
└── 2015
└── 10
└── 18
└── welcome-to-jekyll.html
7 directories, 5 files
# While on gh-pages:
$ tree .
.
├── about
│ └── index.html
├── css
│ └── main.css
├── feed.xml
├── index.html
└── jekyll
└── update
└── 2015
└── 10
└── 18
└── welcome-to-jekyll.html
7 directories, 5 files
And it shall not have any remembrance of master
's history:
# While on gh-pages:
$ git log --pretty=oneline
a05b0365bf2e91e852a0440a230957a975ed7275 New release
# While on gh-pages:
$ git log --pretty=oneline
a05b0365bf2e91e852a0440a230957a975ed7275 New release
Such a branch would be perfect for deployment and could be considered a one-time branch, ie. one would delete it once the website has been deployed.
Note that this workflow is viable for any hosting platform, not GitHub Pages only. As long as you are able to generate
_site/
and deploy it somewhere for a webserver to, well, serve, you're pretty much done. Deployment can use git to push to a remote repository, as is the case with GitHub Pages, but may also leverage (s)ftp/scp/rsync/capistrano/whatever. The core idea is always: compile the site locally then deploy. Even if you may not use git to deploy, tracking the project's history with git locally is a good idea.
git symbolic-ref
GitHub Pages’ documentation once advised folks to create a root gh-pages
branch. Although not the case anymore, I still believe it's a great, elegant solution.
A root branch in our Jekyll context is kind of a "static", master
-based, short-lived branch which will exist within the same git repository, but here with a different content structure than master
's. Such a branch is thus unlike your traditional git checkout -b
generated branch: our goal is for it to not have a proper "history" tracking of what happened in the base master
branch, and to feature custom content which will not necessarily match the shape of the base master
branch.
We can create such a strange beast leveraging git references and the symbolic-ref
and clean
git commands. First, let's "duplicate" master
, its whole content:
# Make sure your are on master, with a clean working directory.
# Generate latest _site/.
$ jekyll build
# Let's "switch" to a gh-pages branch by overriding HEAD's semantic.
$ git symbolic-ref HEAD refs/heads/gh-pages
# Let's make all content untracked by git while on gh-pages, so it may
# be removed at will.
$ rm .git/index
# Make sure your are on master, with a clean working directory.
# Generate latest _site/.
$ jekyll build
# Let's "switch" to a gh-pages branch by overriding HEAD's semantic.
$ git symbolic-ref HEAD refs/heads/gh-pages
# Let's make all content untracked by git while on gh-pages, so it may
# be removed at will.
$ rm .git/index
Then comes the important step: remove everything except _site/
:
# Let's remove all untracked content, but the _site/ folder.
$ git clean -fdx -e _site/
# Let's remove all untracked content, but the _site/ folder.
$ git clean -fdx -e _site/
Be aware running
git clean
like that is a dangerous operation. For one thing it will wipe out any content untracked by git. Prevent this from happening by either tracking or .gitignoring any file or directory. A more advanced workflow could selectively remove lines from .git/index and feed a whitelist of files to remove togit clean
. The simpler the better IMO though. Think "Spiderman" here.
Let's move all of the content at root level, otherwise Jekyll's base_url
would need to include "/_site/".
$ mv _site/* .
$ rm -R _site/
$ mv _site/* .
$ rm -R _site/
You’re pretty much done with generating the "static" gh-pages
branch! At this stage, it contains only the generated website, has no proper history (yet), and is basically ready for deployment… So let's deploy:
$ touch .nojekyll
# Deploying to GitHub Pages for the sake of the example.
# You could deploy to any host, using any method, really.
$ git add .
$ git ci -m "New release"
$ git push origin gh-pages
# Cleaning up.
$ git checkout master
$ git branch -D gh-pages
$ touch .nojekyll
# Deploying to GitHub Pages for the sake of the example.
# You could deploy to any host, using any method, really.
$ git add .
$ git ci -m "New release"
$ git push origin gh-pages
# Cleaning up.
$ git checkout master
$ git branch -D gh-pages
Note an empty .nojekyll file has been added along the way, so as to instruct GitHub not to bother trying to assess whether Jekyll should run. For it should not.
That strategy, although efficient, is a lot of git gymnastic to remember about: it should be encapsulated within a proper deployment script. A raw bash script, capistrano, a Thor task… Below is an example of a deploy.sh
bash script I use to deploy this very website:
#!/bin/sh
# Building release.
jekyll build
git symbolic-ref HEAD refs/heads/release
rm .git/index
git clean -fdx -e _site/
mv _site/* .
rm -R _site/
# Deploying to DigitalOcean.
git add .
git ci -m "New release"
rsync -avzhe ssh --delete --progress . 178.62.87.133:public_html/kaibun.net
# Cleaning up.
git checkout master
git branch -D release
#!/bin/sh
# Building release.
jekyll build
git symbolic-ref HEAD refs/heads/release
rm .git/index
git clean -fdx -e _site/
mv _site/* .
rm -R _site/
# Deploying to DigitalOcean.
git add .
git ci -m "New release"
rsync -avzhe ssh --delete --progress . 178.62.87.133:public_html/kaibun.net
# Cleaning up.
git checkout master
git branch -D release
Works for me!