As the custom dictates, the first post on my Jekyll-powered blog is about how to conveniently deploy Jekyll1 sites – with a twist: my workflow relies on Git to push things to the server and Bundler to manage dependencies.
After following the steps I’ll describe in detail below, you’ll be able to simply
git commit any changes you’ve made to your site and then perform a
git push to deploy those updates. That’s it!
(Also: Any Jekyll plugins you add to your
Gemfile will be installed on the server automatically, and force-pushing won’t break anything.)
What you need
In order for this tutorial to make much sense, you need some basic experience with Jekyll2. You should also be reasonably comfortable working on remote servers using SSH.
Locally, you need a folder containing your Jekyll site and the associated
Gemfile.lock files. Whether or not Jekyll is installed locally doesn’t matter for the deployment process, but since it allows you to test any changes locally, it’s recommended.
On the server where you want to deploy your site, Ruby, RubyGems and Bundler should already be installed. Conveniently, all of these are installed out of the box on Uberspace, which is where I host this blog.
Additionally, you’ll need reasonably up-to-date versions of Git both locally and on your server.
What you have to do
Okay. You’ve finished the initial setup work of getting a Jekyll site up and running – but so far only locally on your laptop. In order to get it up into the cloud3, you need a server. You’ve got that too? Great! Let’s get started.
Because you’ll be using Git to deploy your site, you need two Git repositories: one locally (in which you’ll perform your changes) and another one on the server (where you’ll push your changes, and which will manage the Jekyll build).
Unless you already have a local Git repository, it’s best to create one on the server first. On your server, run the following commands to create a bare repository.
$ mkdir <REPOSITORY NAME>.git $ cd !$ $ git init --bare
To create the local repository, clone the one you’ve just created. Locally, run:
$ git clone ssh://<USER>@<SERVER.TLD>/<ABSOLUTE PATH TO REPOSITORY> $ cd <REPOSITORY NAME>
Now you can move your Jekyll site to that folder and
git add and
git commit your changes. But before pushing them to your server by means of
git push, let me tell you about Git hooks:
Every Git repository contains a hidden
.git/ directory. This is where Git stores pretty much everything it needs to know about that repository: its configuration, the commit history, and among a bunch of other things, a
hooks/ folder. This folder contains
.samples for shell scripts that Git will run before or after specific actions are performed –
pre-push will be executed right before pushing to a remote server,
pre-commit will run before Git registers a commit, and so on. You get the idea: these scripts allow you to hook some custom actions into Git’s internal pipeline.
How can you use this feature? There’s a hook called
post-receive, which Git will conveniently run for you whenever changes are pushed to the containing repository. So you basically just need to call
jekyll build somewhere in the
post-receive file, and Git will take care of kicking off the build process whenever you push changes to the server!
It’s not actually quite that simple, but that’s the basic process. To create the hook, run the following commands on your server:
$ cd <REPOSITORY NAME>.git/hooks $ touch post-receive $ chmod +x post-receive $ nano post-receive
In the text editor that pops up, paste the following shell script4 and save after modifying the first three lines (which I’ll explain below) to match to your setup:
GIT_REPO="$HOME/<REPOSITORY NAME>.git" TMP_GIT_CLONE="$HOME/tmp_<REPOSITORY NAME>" PUBLIC_WWW="<WEBSITE FOLDER>" unset GIT_DIR if [ ! -d "$TMP_GIT_CLONE" ]; then git clone $GIT_REPO "$TMP_GIT_CLONE" cd "$TMP_GIT_CLONE" else cd "$TMP_GIT_CLONE" # avoid breaking the build when force pushing git fetch --all git reset --hard origin/master git pull fi # make sure that everything's installed bundle install --deployment # kick off the build bundle exec jekyll build -s "$TMP_GIT_CLONE" -d "$PUBLIC_WWW" exit
GIT_REPO variable should contain the path to the bare Git repository you’ve previously created on your server. The directory pointed to by
TMP_GIT_CLONE will contain a temporary clone of the bare repository. This directory will be created automatically, so there’s no need to
mkdir it before pushing your first commit! Finally, the
PUBLIC_WWW variable should point to wherever Jekyll should write the finished site to.
The shell script is fairly self-explanatory, so I won’t confuse you by rehashing what it does.
bundle install --deployment will create a folder
$TMP_GIT_CLONE/vendor, where all required Gems will be installed5. Some files in this directory will invariably contain Liquid tags, so it’s best to tell Jekyll to ignore it. Adding the following line to your
_config.yml file does the trick:
Now you can finally run
git push. If everything goes well, you should see something along these lines:
Counting objects: 3, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 337 bytes | 0 bytes/s, done. Total 3 (delta 2), reused 0 (delta 0) remote: Cloning into '/home/doersino/tmp_excessivelyadequate.com' ... remote: Done. remote: Warning: the running version of Bundler (1.13.2) is older than the versi on that created the lockfile (1.13.5). We suggest you upgrade to the latest vers ion of Bundler by running `gem install bundler`. remote: Fetching gem metadata from https://rubygems.org/.......... remote: Fetching version metadata from https://rubygems.org/.. remote: Fetching dependency metadata from https://rubygems.org/. remote: Installing addressable 2.4.0 remote: Installing colorator 1.1.0 remote: Installing ffi 1.9.14 with native extensions remote: Installing forwardable-extended 2.6.0 remote: Installing sass 3.4.22 remote: Installing rb-fsevent 0.9.7 remote: Installing kramdown 1.12.0 remote: Installing liquid 3.0.6 remote: Installing mercenary 0.3.6 remote: Installing rouge 1.11.1 remote: Installing safe_yaml 1.0.4 remote: Using bundler 1.13.2 remote: Installing rb-inotify 0.9.7 remote: Installing pathutil 0.14.0 remote: Installing jekyll-sass-converter 1.4.0 remote: Installing listen 3.0.8 remote: Installing jekyll-watch 1.5.0 remote: Installing jekyll 3.3.0 remote: Installing jekyll-feed 0.8.0 remote: Bundle complete! 3 Gemfile dependencies, 19 gems now installed. remote: Bundled gems are installed into ./vendor/bundle. remote: Configuration file: /home/doersino/tmp_excessivelyadequate.com/_config.yml remote: Source: /home/doersino/tmp_excessivelyadequate.com remote: Destination: /var/www/virtual/doersino/excessivelyadequate.com remote: Incremental build: disabled. Enable with --incremental remote: Generating... remote: done in 0.567 seconds. remote: Auto-regeneration: disabled. Use --watch to enable. To ssh://draco.uberspace.de/home/doersino/excessivelyadequate.com.git/ 9dda0ee..c2285dd master -> master
Addendum: Open-sourceing your site
If you want to push your site to a GitHub repository6 in addition to pushing to your deployment server, the instructions up until the Git hooks part are slightly different. I’ll just show you what I did – you’ll need to modify the URIs to match your setup.
On your server,
git clone --bare the GitHub repository instead of initializing it the way it’s outlined above:
$ git clone --bare https://github.com/doersino/excessivelyadequate.com.git
Then set up the
post-receive hook as explained previously.
Locally, simply create a normal
git clone of the GitHub repository:
$ git clone https://github.com/doersino/excessivelyadequate.com.git
Now you need to tell Git to push your local clone to both the original GitHub repository and the bare repository you’ve just created. You can achieve this by editing the
.git/config file of the local clone. In the
[remote "origin"] section, add your equivalent of the following two lines:
pushurl = https://github.com/doersino/excessivelyadequate.com.git pushurl = ssh://email@example.com/home/doersino/excessivelyadequate.com.git/
Now whenever you
git push, Git will first push to GitHub and then to your deployment server, where the
post-receive hook will fire, which will kick off the Jekyll build. A second or three later, the changes you’ve made will be live on GitHub as well as on your site!
At the time of writing, Jekyll 3.8.3 is the newest release. Future updates might break things. ↩
My first time working with Jekyll was a few days ago, while making the theme for this blog. This post is solely based on that experience – so if you’ve got your own, brand new Jekyll site, you’ll probably be able to follow along easily. ↩
If you’re a Haskell geek: This is roughly analogous to the