For almost two years, I’ve been using GitHub Pages to host my blog. This tool handles a lot of things for you behind the scene: it builds your site and deploys it to a web server. GitHub implements this as a great example of continuous delivery, the marriage of continuous integration with continuous deployment. GitHub also lets you use your own custom domain name, but it doesn’t support TLS connections over them. Thus, I had to take matters into my own hands and go back to self hosting.

Enter my old pals NearlyFreeSpeech.NET, Let’s Encrypt, and Travis CI. This host provides SSH access, and Travis provides easy ways to set up CI/CD pipelines. Let’s get started on building the GitHub Pages site first which itself is based on Jekyll. This static site generator is written in Ruby, so in order to build this, we need a recent Ruby environment. Then a few commands can get you bootstrapped:

gem install bundler
bundle install
bundle exec jekyll serve

This command serves an incrementally compiling view of the site. We can change serve to build to simply get the output. Now we need to deploy these output files via SSH. There are several different ways to copy files over SSH, and in today’s post, we’ll be piping data directly through an SSH pipe.

tar -czf - -C _site . | ssh deploy@ssh.example.org 'tar -xzf - -C /var/www'

By using tar, we bundle up the entire _site/ directory and compress it, and we output to stdout. By changing directories into what we want to compress (using -C), we’ll be able to easily extract it wherever we want. Now that we have the basics, let’s integrate it with Travis.

First, let’s get started with continuous integration of our website. We need to add a minimal .travis.yml config file to declare our pipeline:

language: ruby
cache: bundler

before_install:
  - gem install bundler

script:
  - bundle exec jekyll build

In our minimal config, we declare the programming language in use (Ruby), enable package caching for Bundler, setup commands to prepare the build environment, and build commands. After enabling our repository in Travis, we can now see our site get built on commit. This can help detect errors in configuration or anything verifiable that we add tests for.

Next, we need to configure deployment. Travis provides a simple way to encrypt files to store in a git repository which can be decrypted inside the running Travis build container. We’ll use this to encrypt an SSH private key file to be used for copying our site build artifacts. Let’s set up a deploy key.

echo .travis_deploy_key >>.gitignore
ssh-keygen -C 'deploy@travis-ci.org' -f .travis_deploy_key

Next, we need to install the Travis command line tool and log in.

gem install travis
travis login

Next, we encrypt the file and update our Travis config.

travis encrypt-file .travis_deploy_key -a

This will output a file named .travis_deploy_key.enc which can be safely committed to git. The unencrypted file should not be committed, and the provided example adds it to .gitignore to prevent it as such. Now we can add some more settings to .travis.yml.

language: ruby
cache: bundler

addons:
  ssh_known_hosts: ssh.example.org

before_install:
  - openssl aes-256-cbc ...
  - gem install bundler

script:
  - bundle exec jekyll build

after_success:
  - eval "$(ssh-agent -s)"
  - chmod 600 .travis_deploy_key
  - ssh-add .travis_deploy_key
  - "tar -czf - -C _site . | ssh deploy@ssh.example.org 'tar -xzf - -C /var/www'"

branches:
  only:
    - master

Of note here is the after_success section. The first three commands are used to set up ssh-agent so that any ssh program can use its keys without user input. The last command is the same one from earlier for deploying the site artifacts. We also have a branches section which we use to restrict this pipeline to only execute on the master branch. This way we can use branches to store blog post drafts and other things without them being deployed as well.

We can adapt this process to build unsupported programming languages as well such as LaTeX. To do so, we can use the apt addon for Travis.

addons:
  ssh_known_hosts: ssh.example.org
  apt:
    packages:
      - texlive-full

before_install:
  - openssl aes-256-cbc ...

script:
  - pdflatex cv.tex
  - pdflatex cv.tex

after_success:
  - eval "$(ssh-agent -s)"
  - chmod 600 .travis_deploy_key
  - ssh-add .travis_deploy_key
  - scp cv.pdf deploy@ssh.example.org:/var/www/

branches:
  only:
    - master