A few months back I decided I wanted to blog like a hacker, so I started revamping my old Wordpress blog into a more minimal site generated by Jekyll. You’re reading the result of that work right now.
I was happy with how easy Jekyll was to set up and use, and it’s a relief to have my post archives as markdown files under Git version control instead of in a proprietary blog engine format in some database.
One of the cool things about a Jekyll site is that you can push it to Github, and Github will serve it. Not just the source, but the actual site itself. You can see it working with this site. Here is the source, and here it is running. While this is pretty cool, Github isn’t really an app platform. I wanted to run my site on Heroku.
Through some searching and tinkering I figured out a couple of different ways of getting my site running on Heroku. I’ll share both of them here. The first method is great for its simplicity but is short on configurability and efficiency. The second method is more complicated but is performant and endlessly tweakable.
Method 1: Running the Jekyll Server on Heroku
Heroku’s latest application stack, Cedar, provides an amazing amount of flexibility with some very simple configuration techniques. It makes running your Jekyll site on Heroku as simple as running it on your own computer – you install some gems and run the server.
I used this example app created by Mark Pundsack as a guide. Its REAME explains the setup process very well. Here is a simple series of steps to get up and running for free on Heroku, assuming you already have a working Jekyll site.
Create a Heroku app on the Cedar stack
From the top level of your Jekyll site’s repo, create the Heroku site. This will provision the Heroku site and add a git remote named “heroku”.
1 heroku create -s cedar
Install the gems
Create a Gemfile that looks like this:
1 2 3 4 source :rubygems gem 'jekyll' gem 'RedCloth'
1 bundle install
to install the gems and create a Gemfile.lock file.
Configure the Jekyll server
Apps on the Cedar stack define their process types via a Procfile. Your Procfile needs one line defining the Jekyll server like this:
1 web: bundle exec jekyll --server -p $PORT
Deploy to Heroku
1 git push heroku master
That’s it! Now your Jekyll site is running on Heroku.
Method 2: Running Your Site As a Rack App
While the method above is simple and straightforward, it’s not the ideal way to run the site. The Jekyll server isn’t meant for production as it generates pages on the fly for every request. I wanted to take advantage of the real power of Jekyll, its ability to generate a static site that can be served efficiently.
Instead of using the Jekyll server, I really wanted to serve my site as a Rack app. That way I could choose my own app server and have a great deal more control over things like caching using Rack middleware.
Setting Up rack-jekyll
Luckily this can be done quite easily using the rack-jekyll project. Here’s how to set it up.
First add the gem to your Gemfile. I also added the Thin gem, so I could use it as my server. Now your Gemfile should look something like this:
1 2 3 4 5 6 source :rubygems gem 'jekyll' gem 'rack-jekyll' gem 'RedCloth' gem 'thin'
Next set up a config.ru file that looks like this:
1 2 3 require 'rack/jekyll' run Rack::Jekyll.new
Finally, modify your Procfile to run your new Rack app. With Thin, mine looks like this:
1 web: bundle exec thin start -p $PORT -e $RACK_ENV
Building Your Site the Right Way
The documentation for rack-jekyll suggests building the files and then committing the _site directory to your repo before deploying to Heroku. While that will work, it’s a bit messy. I really didn’t want builds to be checked into version control.
An alternative would have been letting Jekyll build the site on the first request to each dyno, but that’s no good either, because it mixes the build and run phases. Plus, depending on writes across multiple requests is not the intended use of Heroku’s ephemeral writable filesystem and could lead to bugs.
What I really needed was to build the site during Heroku’s build phase. This can actually be done by leveraging a new experimental feature known as a “custom buildpack.” Buildpacks are the key to Heroku’s polyglot platform. Each language or framework supported on Heroku such as Ruby, Node.js, or Clojure has it’s own buildpack containing the tools necessary to build an app.
In a normal deployment Heroku automatically detects what buildpack to apply, but you can actually tell Heroku to use a specific buildpack. Either pass an option for it when you create the app:
1 heroku create -s cedar --buildpack [URL]
or provide the URL for the buildpack repo using the BUILDPACK_URL config variable.
Since it already had all the tools for Rack apps, I forked the Ruby buildpack and added a few lines so that my Jekyll site will be built by Heroku during slug compilation. Finally I set my BUILDPACK_URL with the following command:
1 heroku config:add BUILDPACK_URL=http://github.com/mattmanning/heroku-buildpack-ruby-jekyll.git
Now when I deploy I see an extra “Building jekyll site” line in the output,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 [~/code/blog] git push heroku master Counting objects: 12, done. Delta compression using up to 2 threads. Compressing objects: 100% (8/8), done. Writing objects: 100% (8/8), 1.10 KiB, done. Total 8 (delta 4), reused 0 (delta 0) -----> Heroku receiving push -----> Fetching custom build pack... done -----> Ruby/Rack app detected -----> Installing dependencies using Bundler version 1.1.rc Running: bundle install --without development:test --path vendor/bundle --binstubs bin/ --deployment Fetching gem metadata from http://rubygems.org/....... Using RedCloth (4.2.8) Using posix-spawn (0.3.6) Using albino (1.3.3) Using fast-stemmer (1.0.0) Using classifier (1.3.3) Using daemons (1.1.4) Using directory_watcher (1.4.1) Using eventmachine (0.12.10) Using kramdown (0.13.3) Using liquid (2.3.0) Using syntax (1.0.0) Using maruku (0.6.0) Using jekyll (0.11.0) Using rack (1.3.5) Using thin (1.3.1) Using bundler (1.1.rc) Your bundle is complete! It was installed into ./vendor/bundle Cleaning up the bundler cache. Building jekyll site -----> Discovering process types Procfile declares types -> web Default types for Ruby/Rack -> console, rake -----> Compiled slug size is 7.2MB -----> Launching... done, v47 -----> Deploy hooks scheduled, check output in your logs http://www.mwmanning.com deployed to Heroku To email@example.com:mattmanning.git 8f84bc4..9350a12 master -> master
and my site runs as a Rack app without polluting the repo with build files!