jahed.dev

Automating Deployments to GitHub Pages with Middleman and Travis CI

I have a Middleman project and I want to push to GitHub Pages. How can I ease the process of making changes to the project's source files and publishing its generated static files?

Recently, I've been using Middleman to build static websites. I've dwelled into static site generators in the past but never really got into it until I started to build an actual website, the Unruly Tech site, which has multiple, often similar but slightly different pages. Copying and pasting became a chore and I didn't want to run a full blown server to compile templates due to the extra management. Hosting on GitHub Pages was enough.

Previously, working with GH Pages was simple enough. Just make changes to static assets and push them to gh-pages. But now with Middleman being the middleman, I had to make changes, compile the static files locally and then push those to gh-pages. What a chore.

Of course you could write a script to do it all in one command, but that also meant other contributors had to do the same and have a compatible environment with dependencies. That's way too much setup for a simple setup.

Build Logs from Travis CI's Web Interface

Now comes Travis CI. Travis is an automated build server, free for open source projects. It hooks into a repo and automatically runs scripts in a defined environment when something's pushed. With this, I now had a common environment to deploy from with minimal faff.

Note: Some dead links on this post have been removed.

Alternative Routes

I know that GH Pages has Jekyll integration to ease the process, but having tried both Jekyll and Middleman, I prefer Middleman in pretty much every aspect but that's for a different blog post

There's also some other continuous integration services available but they're all somewhat similar and I'm already familiar with Travis.

Let's start

Our goal is to be able to push changes to a Middleman project, let Travis pick it up, build and test it and deploy it to a GitHub Pages branch.

Setup the project on Travis

First, Make sure you have a Travis account and your project is visible to it by going to Travis CI's website.

Back in the root of your project, make sure you have Travis CLI installed and you're logged in:

gem install travis
travis login

Next you'll want to restrict your concurrent builds so only one is built at a time. This will prevent multiple commits being deploy out of order and causing race conditions. In the root of your project, run:

travis settings maximum_number_of_builds --set 1

Setup Middleman Deploy

First, you'll need to use the middleman-deploy extension if you haven't already and activate it with the following in your project's config.rb:

activate :deploy do |deploy|
  deploy.method = :git
  deploy.branch = 'gh-pages' # or master

  committer_app = "#{Middleman::Deploy::PACKAGE} v#{Middleman::Deploy::VERSION}"
  commit_message = "Deployed using #{committer_app}"

  if ENV["TRAVIS_BUILD_NUMBER"] then
    commit_message += " (Travis Build \##{ENV["TRAVIS_BUILD_NUMBER"]})"
  end

  deploy.commit_message = commit_message
end

Here we're simply setting up middleman-deploy to commit with the Travis Build Number as the message so we know where the commit came from.

If your GitHub Page is for the top-level (i.e. username.github.io) then you'll want to set deploy.branch to master and commit your Middleman project to a different branch like develop.

Give Travis commit access to your project

In order for Travis to be able to commit to your GitHub Pages branch, you need to setup an SSH key for it.

Create an SSH Key for Travis

In the root of your project, create your SSH keys by running:

ssh-keygen -q -N "" -f deploy_key
travis encrypt-file deploy_key
rm -f deploy_key

You will now have these new files:

Copy and paste the contents of deploy_key.pub as a new Deploy Key in your GitHub Repo by going to Settings > Deploy keys in GitHub. Make sure to give it write access.

Once you've save it, you can remove the public key.

rm -f deploy_key.pub

The travis encrypt-file command which you ran gives you a decryption command to run later:

openssl aes-256-cbc -K $encrypted... etc.

Keep note of this! We'll be using it later.

Setup the Deploy Script for Travis

Now that we have the SSH key setup, we need to use it when we deploy from Travis.

Create a new script called travis-deploy.sh at the root of your project and give it execution rights:

touch travis-deploy.sh
chmod +x travis-deploy.sh

Set the contents of the script to be as follows:

#!/usr/bin/env bash

if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then
    echo "Not deploying pull request."
    exit
fi

echo -e "\nRunning Travis Deployment"
echo "Setting up Git Access"
openssl aes-256-cbc -K $encrypted... etc. # Copy and paste the command we noted before here!
chmod 600 deploy_key

# Add the SSH key so it's used on git commands
eval `ssh-agent -s`
ssh-add deploy_key

HTTPS_URL=$(git config remote.origin.url)
SSH_URL=${HTTPS_URL/https:\/\/github.com\//[email protected]:}
git remote set-url origin "${SSH_URL}"

git config --global user.name ${GH_COMMIT_AUTHOR}
git config --global user.email ${GH_COMMIT_EMAIL}

bundle exec rake deploy

This script will decrypt the encrypted private key, give it safe permissions and add it to the script's session. It then sets up the git repo to use SSH and runs the deploy task in the same session via Rake which we'll get to next.

Make sure you've replaced the line with the comment with the output travis encrypt-file gave you before.

Setup some Rake tasks

task default: %w[test]

task :test do
  puts "\nBuilding project"
  try "middleman build"
end

task :deploy do
  puts "\nDeploying to GitHub"
  try "middleman deploy"
end

namespace :travis do
  task :script do
    Rake::Task["test"].invoke
  end

  task :after_success do
    try "./travis-deploy.sh"
  end
end

## Helper so we fail as soon as a command fails.
def try(command)
  system command
  if $? != 0 then
    raise "Command: `#{command}` exited with code #{$?.exitstatus}"
  end
end

Here we're defining two tasks for Travis:

1. travis:script

Test task for the Travis build. If this fails, no deploy happens. Our test case is if the Middleman build succeeds, relying on Middleman to tell us if something's wrong.

2. travis:after_success

Deploy task after the test task succeeds. In our case, we'll run the travis-deploy.sh script we created earlier.

Define your .travis.yml

Now that we have our Rake tasks, it's just a matter of plugging them in.

language: ruby
cache: bundler
rvm:
  - 2.1.0
branches:
  only:
    - master # branch to build
env:
  global:
    - GH_COMMIT_AUTHOR="Travis CI"
    - [email protected]
script:
  - bundle exec rake travis:script
after_success:
  - bundle exec rake travis:after_success

Pretty self-explanatory. It will run our test task first (script), then on success it will deploy it (after_success)!

You'll want to only run the build on the branches holding your unbuilt Middleman project, whether it's master, develop or whatever.

You'll also want to setup some environment variables which travis-deploy.sh uses:

Make sure everything's ready

By the end of all this, you should have at least the following files committed in the root of your project:

Ensure no private details are committed such as unencrypted private keys and tokens.

Run it

That should be it. Commit a change to your Middleman project and watch Travis pick it up and deploy it. Watch the build and check the logs to see if anything goes wrong or any secrets are leaked.

Now you have a Middleman project setup to use GitHub Pages with automated deployment and reduced chance to publish a broken build.

To see all this in action, check out the Unruly Tech website which is open source and hosted on GitHub Pages.