Jekyll on Heroku

Jekyll, the static website generator written in Ruby and popularized by GitHub, is a great candidate for being run on Heroku. Originally built to run on GitHub Pages, running Jekyll on Heroku allows you to take advantage of Jekyll’s powerful plugin system to do more than convert Markdown to HTML. On my blog, I have plugins to download my Goodreads current and recently read books and to generate Open Graph images for posts. That said, it’s not straightforward to get up and running on Heroku without using jekyll serve to do the heavy lifting. jekyll serve uses Ruby’s built-in, single threaded web server WEBrick, but a public site should be using a web server more suited for production, like nginx.

We’ll start from the very beginning. You’ll need Ruby and Bundler installed.

I like ruby-install and chruby as my Ruby installer and switcher.

This is the platform-agnostic way

installing the latest version of ruby-install. If you prefer, you can find instructions for OS X and Windows in ruby-install’s README.

The commands below use the most recent versions of ruby-install and chruby available when this post was published. You probably want to use more recent versions if available.

$ wget -O ruby-install-0.6.1.tar.gz https://github.com/postmodern/ruby-install/archive/v0.6.1.tar.gz
$ tar -xzvf ruby-install-0.6.1.tar.gz
$ cd ruby-install-0.6.1/
$ sudo make install

You’ll also want chruby, which you can similarly install in a more platform specific way using instructions in chruby’s README.

$ wget -O chruby-0.3.9.tar.gz https://github.com/postmodern/chruby/archive/v0.3.9.tar.gz
$ tar -xzvf chruby-0.3.9.tar.gz
$ cd chruby-0.3.9/
$ sudo make install

Install the latest Ruby and use it:

$ ruby-install ruby
$ chruby ruby

You’ll need Bundler and the Jekyll gem:

$ gem install bundler jekyll
Fetching: bundler-1.9.9.gem (100%)
Successfully installed bundler-1.9.9
Fetching: jekyll-3.6.2.gem (100%)
Successfully installed jekyll-3.6.2
2 gems installed

Generating a new jekyll site is straightforward, and you get a site template for free similar to starting a new Rails app. We’ll be saving our work in Git along the way:

$ jekyll new jekyll-on-heroku
Ignoring nokogiri-1.8.1 because its extensions are not built.  Try: gem pristine nokogiri --version 1.8.1
Running bundle install in /Users/caleb.thompson/code/jekyll-on-heroku/_rundoc/tmp/jekyll-on-heroku...
  …
New jekyll site installed in /Users/caleb.thompson/code/jekyll-on-heroku/_rundoc/tmp/jekyll-on-heroku.
$ cd jekyll-on-heroku
$ git init
Initialized empty Git repository in /Users/caleb.thompson/code/jekyll-on-heroku/_rundoc/tmp/jekyll-on-heroku/.git/
$ git add .
$ git commit -m "jekyll new jekyll-on-heroku"
[master (root-commit) ea8cd80] jekyll new jekyll-on-heroku
 8 files changed, 206 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 404.html
 create mode 100644 Gemfile
 create mode 100644 Gemfile.lock
 create mode 100644 _config.yml
 create mode 100644 _posts/2017-12-07-welcome-to-jekyll.markdown
 create mode 100644 about.md
 create mode 100644 index.md

We have our Jekyll site (go ahead and try jekyll serve if you’d like), but we’ll need a Heroku app to deploy to. You will need an account and the CLI installed before this step. We’re going to use the official Ruby buildpack as well as Heroku’s (unofficial) Static buildpack to do the site generation and static page serving, respectively:

$ heroku create
Creating app... done, radiant-bastion-50307
https://radiant-bastion-50307.herokuapp.com/ | https://git.heroku.com/radiant-bastion-50307.git
$ heroku buildpacks:add heroku/ruby
Buildpack added. Next release on radiant-bastion-50307 will use heroku/ruby.
Run git push heroku master to create a new release using this buildpack.
$ heroku buildpacks:add https://github.com/heroku/heroku-buildpack-static
Buildpack added. Next release on radiant-bastion-50307 will use:
  1. heroku/ruby
  2. https://github.com/heroku/heroku-buildpack-static
Run git push heroku master to create a new release using these buildpacks.

Heroku’s buildpacks need specific files to be present to work correctly. We already have Gemfile for the Ruby buildpack, but we’ll also need a static.json file for the static buildpack. I’ve added a few of my favorite options to the static.json, and we’ll need to use _site/ for our root as that is where Jekyll builds its static content by default.

In file static.json write:

{
  "clean_urls": true,
  "https_only": true,
  "root": "_site/"
}

Let’s save our progress again.

$ git add static.json
$ git commit -m "Add static.json with reasonable defaults"
[master 6e1dea7] Add static.json with reasonable defaults
 1 file changed, 5 insertions(+)
 create mode 100644 static.json

The Ruby buildpack runs rake assets:precompile when it’s available. This is inspired by Rails’ pattern, but it works for any assets you’ll need. We’re going to use it for our entire site by running jekyll build.

In file Rakefile write:

task "assets:precompile" do
  exec("jekyll build")
end

You’ll need Rake to run this in the first place, and we can save ourselves trouble in the future by specifying a version.

At the end of Gemfile add:

gem "rake"
ruby "2.4.2"

And run bundle to install Rake.

$ bundle
  …
Bundle complete! 5 Gemfile dependencies, 24 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

We need to add the changes for the Rake task and new dependencies:

$ git add Rakefile Gemfile Gemfile.lock
$ git commit -m "Add Rake task to build Jekyll site"
[master 94f664b] Add Rake task to build Jekyll site
 3 files changed, 10 insertions(+)
 create mode 100644 Rakefile

Finally, we can push the app to Heroku!

$ git push heroku master
warning: not sending a push certificate since the receiving end does not support --signed push
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Ruby app detected
remote: -----> Compiling Ruby
remote: -----> Using Ruby version: ruby-2.4.2
remote: -----> Installing dependencies using bundler 1.15.2
remote:        Running: bundle install --without development:test --path vendor/bundle --binstubs vendor/bundle/bin -j4 --deployment
  …
remote: -----> Detecting rake tasks
remote: -----> Precompiling assets
remote:        Running: rake assets:precompile
remote:        Configuration file: /tmp/build_c29844d07a39f5ce14fd64c34fc17680/_config.yml
remote:        Source: /tmp/build_c29844d07a39f5ce14fd64c34fc17680
remote:        Destination: /tmp/build_c29844d07a39f5ce14fd64c34fc17680/_site
remote:        Incremental build: disabled. Enable with --incremental
remote:        Generating...
remote:        done in 0.901 seconds.
remote:        Auto-regeneration: disabled. Use --watch to enable.
remote:        Asset precompilation completed (1.49s)
remote:
remote: ###### WARNING:
remote:        No Procfile detected, using the default web server.
remote:        We recommend explicitly declaring how to boot your server process via a Procfile.
remote:        https://devcenter.heroku.com/articles/ruby-default-web-server
remote:
remote: -----> Static HTML app detected
remote:   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
remote: 100  838k  100  838k    0     0  5598k      0 --:--:-- --:--:-- --:--:-- 5628k
remote: -----> Installed directory to /app/bin
remote: -----> Discovering process types
remote:        Procfile declares types     -> (none)
remote:        Default types for buildpack -> web
remote:
remote: -----> Compressing...
remote:        Done: 26.4M
remote: -----> Launching...
remote:        Released v3
remote:        https://radiant-bastion-50307.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/radiant-bastion-50307.git
 * [new branch]      master -> master

Open the URL at the end and enjoy your new Jekyll app on Heroku!

Leave a Reply

Your email address will not be published. Required fields are marked *