GitHub pages allows you to publish a website/documentation by just pushing some markdown files on a git repository. It means that you can focus on content and not on the underlying tool, infrastructure and deployment process.

GitHub pages are based on Jekyll which allow a total control on how is rendered the content. This is great but how about private content? GitHub pages are public and no access control is possible.

To bypass this limitation, the idea is to deploy the website on Heroku (it’s free and fully managed too). For the writer it doesn’t change anything, each time new content is pushed on the branch the website will automatically be redeployed. For the readers, it will require them to be in the authorized github organization to access to the content.

Here are the steps in order to make your github pages private for free.

1. Create your Jekyll site

That goes without saying but you will need to generate at least a Jekyll website skeleton using jekyll new. Checkout the documentation for more details.

All the files need to be pushed a repository from the github organization that will have access to the pages.

2. Register Jekyll Auth plugin

Jekyll Auth is a jekyll plugin that provide a simple way to add a OAuth authentication layer on top of your website.

Just add this in your Gemfile (at the root of your repository):

source "https://rubygems.org"

gem 'jekyll-auth'

Then install it with a bundle install.

We are now able to generate the necessary files bundle exec jekyll-auth new. This commands generated three files Gemfile.lock, Rakefile and config.ru. They will be used by Heroku to know how to deploy the app.

As these files are in the jekill folder they will be treated as a “normal” content: files will be copied to the generated website and publicly available.

As the lock file takes a snapshot of your installed ruby dependencies it may leak valuable informations to attackers.

To exclude those files, add the following line in your _config.yml:

exclude: ["Genfile", "Genfile.lock", ".gitignore", "Rakefile", "config.ru", "vendor"]

You may have notice that I’ve also excluded vendor, I will speak about this later.

3. Create an Heroku app

Now you have to create a Heroku app that will host the pages.

If you want to automatically deploy the app when new content is pushed just sync a repository to the app (Deploy pane in the app dashboard).

sync pane

4. Create a GitHub OAuth app

To let only the user from the organization access to the pages an OAuth layer is added on top of the jekyll website deployed on Heroku.

In order to do that you must register a new application in your GitHub application settings.

gh oauth app

The homepage URL is the Heroku app one (endings with .herokuapp.com). The authorization callback URL is your homepage URL + /auth/github/callback.

You now have an a client ID and a client secret ID for your application.

5. Setup your Heroku app

Your pages are nearly up, just add variables to tell to the OAuth layer where to check user.

Add GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET with the keys provided by GitHub in your application page and finally set GITHUB_ORG_ID to the name of your organization.

config var

Those variables can be set either from the settings pane of your application or with the heroku config:set command from the Heroku toolbelt.

6. Monitor the app doing all the job for you

Your pages will now be deployed each time you push something on the sync branch (if defined one), else it will be each time you push on the Heroku remote server (git push heroku master).

In both case you can monitor whats going on in the activity tab of you application.

activity pane

You should not worry much about your app since the pushed content render locally with a jekyll serve.

To get back on the reason why we excluded vendor at the beginning, it is because when the website is generated on Heroku, Jekyll will try to find all the possible markdown file in his path. On Heroku the installed libraries are in the same directory as the website that cause Jekyll to fail with an error like this:

Invalid date '0000-00-00': Post '/vendor/bundle/ruby/...

Limitations

The described solution works without much effort and for free, but you have to be aware that the resources allocated to your website by Heroku is limited (which is totally understandable). As it is a static website it does not require many resources but Heroku dynos are paused after one our of inactivity which means that the first user that access to the website after a period of inactivity may experience a few second delay. Not terrible in this case as you are building a restricted website so your user may be sympathetic but need to be considered.