As I’ve mentioned before, this blog is build as a static website with Jekyll, which allow me not to worry about data bases, logins or anything of the sorts when I’m publishing and managing the web. I use the free tier of GitHub Pages as hosting, which has its downsides and upsides. The main upside is, it’s really convenient and free. On the other hand, I’m limited to certain Jekyll plugins if I want to use GitHub Pages building capabilities. This can be circumnavigate if you use CI (Continuous Integration) to build your site.
What is Continuous Integration?
You can read more about this in the Wikipedia —and in Travis documentation— but CI is basically —and I’m summarizing a lot here— just testing your code before you integrate —merge— it with the master branch, so you know that you aren’t breaking anything —serious. Usually what you do is building your copy and then you merge it with the
master branch. However, building is a pain, and even more if you have to do it all the time —continuously. For this reason, the CI services were born.
Usually a CI works as it follows —again simplified:
- It’s usually related / connected to your online repo and when you push commits to the repo the webhooks are triggered and CI comes into action.
- CI download the last commits of your repo to a machine —usually virtual— with an OS you can specify and begin tu run a series of scripts1 to test and to build your software from the last commit you’ve pushed.
- If those scripts give an error, so your code can’t be build, report to you as failing and —bad luck— you have to review what you have done because it’s not working.
- If the scripts exit without an error —you’re lucky— and now a couple of things can happen.
- Just nothing else. You are notify there is not errors in your code —so you are free to merge.
- The code is automatically deployed.
- It’s merged into another branch —usually
- It’s deploy to a server or something else.
- It’s merged into another branch —usually
All of these steps have a lot of sense in GitHub, GitLab and other repo online services where webhooks can be triggered even with a pull request, so you can know beforehand if someone else code is worth to review in an even of a pull request.
That’s more of less the official function of the CI. However, it can do much more than that. For example, in the Homebrew repos/taps it builds the bottles and deploys them to a hosting so you can latter download them. It can build software and apps and deploy it to your website. Or like in our case can build your Jekyll site and deploy to GitHub Pages.
There are several options out there as CI services, from Travis —with is one of the most common one— to Jenkins or, if you use GitLab, their own CI. I’m going to talk about Travis, because it’s the one I’m using myself and usually is the easier option. You can use Travis for free on the condition your project is open source.
Give access to Travis
First of all, you should go to the Travis website and authorize them to have access to your repos in GitHub. Usually, you just need to login with your GitHub account; they will ask for permission and if you just want to use Travis in all your repos or in just some of the repos. I have it set for only the repos I’m interested on because otherwise Travis can be trigged just pushing a commit to GitHub2.
Configure your build
Once you have given access to Travis to the repo where you have your Jekyll page, you need to configure your build. You configure your build with the file
.travis.yml in the root of your repo.
Here is mine right now:
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 43 44 # .travis.yml # This file should be at the root of your project # language: ruby rvm: - 2.5.3 cache: bundler: true # directories: # - $TRAVIS_BUILD_DIR/tmp/.htmlproofer before_install: - gem install bundler gemfile: Gemfile script: - if [ "$TRAVIS_BRANCH" = "master" ]; then bundle exec jekyll algolia; JEKYLL_ENV=production bundle exec jekyll build; bundle exec htmlproofer ./_site --alt_ignore "/.*/" --allow_hash_href --http-status-ignore 999 --disable-external; fi - if [ "$TRAVIS_BRANCH" = "develop" ]; then JEKYLL_ENV=production bundle exec jekyll build; bundle exec htmlproofer ./_site --alt_ignore "/.*/" --allow_hash_href --http-status-ignore 999 --disable-external; fi branches: only: # Change this to gh-pages if you're deploying using the gh-pages branch - master - develop env: global: - NOKOGIRI_USE_SYSTEM_LIBRARIES=true # speeds up installation of html-proofer sudo: false # route your build to the container-based infrastructure for a faster build deploy: provider: pages skip-cleanup: true github-token: $GITHUB_TOKEN # Set in the settings page of your repository, as a secure variable keep-history: false local_dir: _site # repo: luispuerto/luispuerto.net # in case I wanted to build my page in other repo target-branch: gh-pages on: branch: master
You can learn more about how to create your own configuration file on Travis Documentation, but I’ll walk you through my config:
language: The language we are going to use in the build. In our case is Ruby, because Jekyll is build in Ruby.
rvm: is the version of ruby we want to use. I’m using the last one —at this moment— 2.5.3. I don’t know if you can set it up to use the last stable one, but some people argue that it isn’t a good idea and you should set the version so it match the one in your machine —which has sense
cache: You can cache content that usually don’t change. In our case we are caching the bundle install because usually the gems we need don’t change that often.
before_install: Before we install the gems we are going to need to build our page we need to install the
gemfile: Location of the gemfile, so Travis knows what gems should install.
script: This is the script to build. In ruby the default script is your rake file, but you can define anything else here —like a shell script which is much more simple. As you see I have a couple of entries here that run depending on the branch it’s pushed.
- When I push
masterI run Algolia, build the site and then past the htmlproofer.
- When I push
developI just build the site and past the htmlproofer.
Please remember to build your site with
JEKYLL_ENV=productionbecause if you don’t some features aren’t going to show in the final deployment.
- When I push
branches: This define which branches trigger the build. In my case
develop, but you can add/remove whatever you want.
env: You can define environment variables and in my case since I’m using htmlproofer it’s recommended to use
NOKOGIRI_USE_SYSTEM_LIBRARIESas environment variable.
sudo: We don’t need sudo, since we don’t need customizations.
This is the last part of the config file, which is going to deploy the final built product —your website— to your repo again but in a different branch —
gh-pages in my case. Travis has a section in its documentation about deployment in GitHub Pages.
Just a couple of notes:
on: States the branch is going to trigger the build it’s the previous test exit with no errors. In other words, only when you push commits to
masterTravis is going to deploy your site. If you run Travis in other branches the deployment isn’t going to happen.
target_branch: This is where is going to be deployed your site. In my case I have a branch only for deployments
gh-pageswhich I don’t even have locally. Be aware that Travis push with
--forceto that branch so it’s going to overwrite the history in that branch leaving just one commit.
repo: You can deploy in a different repo if you want, just define username and name of the repo.
local_dir: Folder from the build that is needed to be deployed,
_sitein my case.
keep-history: If you don’t want Travis to push with
--forceyou can set this option to true and it’ll will keep the history of your branch.
github-token: You need to provide Travis with a GitHub personal access token with the
reposcope, depending if you repo is public or private. This token should be private and the best way use it is to configure it in your repository settings in Travis. If you don’t provide the token correctly, Travis isn’t going to be able to deploy.
skip-cleanup: You need to set this up as
trueif you don’t want Travis to delete everything after it finish the built, which wind up deploying nothing.
provider: pages since you are deploying to GitHub Pages.
Perhaps you are wondering why perform the building in your different branches and so. Well, have three permanent branches:
writing. I usually work on
writing and then I merge to
master. I also use Git flow for making changes in the source code of the site, and I’m thinking to try to implement it also for writing post and pages.
In other words, I think it’s a good idea to test that everything add up before I merge to
master when I’m working on features. Writing, it’s much more simple, and usually the errors are just that some link isn’t correctly set, which is easy to fix. I sometimes use htmlproofer locally when I’m writing so I know everything is OK before I merge to
At this moment, I have my configuration exactly like you can see above. However, while I was writing this post, I just discovered that you can use LFS with Travis —of course you can, why wouldn’t you? You can’t use LFS with GitHub Pages though, but I wonder if you implement the build of your site with Travis while using LFS, you’ll make it possible. Anyhow, you need to take into account the bandwidth limitations of LFS and it’s probably that, every time you build with Travis you’ll use part of your bandwidth.
I’ll try to write a post in the near future about what is LFS (Large File Storage), but summing it up a lot, it’s just a system that allow you to manage better large binary files with Git, storing them in alternative storage instead of just tracking them with Git.
These scripts can be whatever you want —more or less. ↩
IIRC to trigger Travis you also need to have a
.travis.ymlfile in the
rootof your repo but I’m not sure. Anyhow… sometimes I have repos in GitHub that have files
.travis.ymland I don’t want they run in CI. In the end you can do whatever you think it’s more convenient for you. I don’t think it’s wise to try to build every time you are pushing to your repo and then receive an email of your build is failing!. ↩