Build your Jekyll site and Deploy it on GitHub Pages with an Azure DevOps pipeline
So far I have been using Travis-CI as my Continuous Integration (CI) system for my build and release process for my Blog. However recently, VSTS got rebranded to Azure Pipelines, and I felt everything got mature enough to finally migrate to Azure Pipelines. To my surprise this turned out to be super easy! Let's see how it can be done!
What do we want to achieve? Well we want to achieve a continuous flow of development, where when we develop a piece of code, the following steps are followed (as illustrated below):
- SCM: Use our SCM protocol to push our code
- PULL CODE: Our source control repository, triggers an event
- BUILD: This trigger gets detected by our Pipeline process which builds our code
- TEST: The Build gets tested
- FAIL/SUCCESS: This results in a failure or success
- RELEASE: On success we release our code to the world
Pipeline Definition
Translating this continuous flow of development to our own project, what we want to do is that as soon as a commit is pushed on to the development
branch (which contains our .md
blog files), we want to trigger a build that is going to transform these to .html
files and push those onto the master
branch. Writing this out in steps would look like:
- Trigger: On push to
development
branch - Setup our git repository
- Build our Jekyll site to
/tmp
throughjekyll build -d /tmp
- Copy over everything from
/tmp
to our working folder (replacing everything except the.git/
folder) - Create a commit and push it to remote
Pipeline Coding
To code this actual pipeline utilizing Azure DevOps
, we can utilize a azure-pipelines.yml
file that we place in the root of our repository. We can now describe what should happens using the steps:
in this files, let's do this in phases:
Step 1: Trigger on-push to Development Branch
Describing to trigger on push is just 2 lines of code:
trigger:
- development
Step 2: Set pool & global variables
Since GitHub wants us to utilize a PAT token to be able to trigger a page build, we will need to add this to our environment variables (as a secret! - see https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables for more information).
We can also define a pool (execution environment) that our code will run on.
pool:
vmImage: 'Ubuntu-16.04'
# Note: github_pat should be configured as an environment variable in devops
# -> create github pat here: https://github.com/settings/tokens
# -> Create environment variable in dev.azure.com under pipelines -> edit (right top) -> variables (right top triple dots) -> called github_pat -> click the lock
variables:
gh_user: thebillkidy
gh_repo: thebillkidy.github.io
gh_pass: $(github_pat)
gh_email: [email protected]
gh_auth_header: $(echo -n "${gh_user}:$(github_pat)" | base64);
Step 3: Pull our Code
Since we set up the PAT token and other variables, we can now start cloning our directory. Let's define the steps for this
Note: We clone our directory rather than using the checkout
function due to the PAT token being required for a page build (see previous step).
steps:
# https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azdevops&tabs=schema#checkout
- checkout: none # we are not going to sync sources, we will manually clone
persistCredentials: false # We disallow setting the persisting, since we want to have a verified push which requires a PAT token
# 1. Setup our local repository and branch that we can work on
- script: git clone https://$(github_pat)@github.com/$(gh_user)/$(gh_repo).git .
workingDirectory: $(Build.StagingDirectory)
displayName: "[Git] Clone GitHub Pages Repository"
- script: |
git config user.email $(gh_email)
git config user.name $(gh_user)
workingDirectory: $(Build.StagingDirectory)
displayName: '[Git] Configure User'
- script: 'git checkout development'
workingDirectory: $(Build.StagingDirectory)
displayName: '[Git] Set development branch'
Step 4: Build our Jekyll Website
We can now build our Jekyll website, to do this we will utilize a /tmp/source
and /tmp/build
directory to be able to generate clean .html
diffs in our commit messages on master.
# 2. Configure Ruby
- task: UseRubyVersion@0 # See: https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/tool/use-ruby-version?view=azdevops
displayName: '[Ruby] Use Ruby >= 2.5'
# 3. Build Site - We only want a HTML diff on master, so we follow this process
# 1. Copy files from development branch to /tmp/source
# 2. Build Jekyll on /tmp/source to /tmp/build
# 3. Remove all files in $(Build.StagingDirectory) except .git/
# 4. Copy everything from /tmp/build to $(Build.StagingDirectory)/
# 5. Create the commit and push it
- script: 'git checkout development'
workingDirectory: $(Build.StagingDirectory)
displayName: '[Git] Switch to development branch for $(Build.StagingDirectory)'
- script: 'mkdir /tmp/source; cp -R * /tmp/source; rm -rf /tmp/source/.git'
workingDirectory: $(Build.StagingDirectory)
displayName: '[Script] Copy file from development branch to /tmp/source'
- script: 'git checkout master'
workingDirectory: $(Build.StagingDirectory)
displayName: '[Git] Switch to master branch for $(Build.StagingDirectory)'
- script: 'gem install bundler'
workingDirectory: /tmp/source
displayName: '[Jekyll] Install Bundler'
- script: |
ls -la;
bundle install
workingDirectory: /tmp/source
displayName: '[Jekyll] Install Jekyll and Dependencies'
- script: |
mkdir /tmp/build;
bundle exec jekyll build -d /tmp/build;
workingDirectory: /tmp/source
displayName: '[Jekyll] Build Jekyll Static Site from /tmp/source to /tmp/build'
- script: |
cp -R $(Build.StagingDirectory)/.git /tmp/build;
rm -rf $(Build.StagingDirectory)/*;
cp -R /tmp/build/* $(Build.StagingDirectory);
workingDirectory: /tmp/build
displayName: '[Script] Remove all files in $(Build.StagingDirectory) except .git/ and add files from /tmp/build'
Step 5: Push our changes
Last but not least is the easiest part, pushing it all to master:
# 3. Create our commit, merge into master, delete draft branch and push it
- script: |
git add --all;
git commit -m"Pipelines-Bot: Updated site via $(Build.SourceVersion)";
workingDirectory: $(Build.StagingDirectory)
displayName: '[Git] Creating commit'
- script: |
git push origin master;
workingDirectory: $(Build.StagingDirectory)
displayName: '[Git] Push changes to remote'
Step 6: Checking Pages Build
As an extra, we would like to utilize the PAT token to check our pages build and see if it was successful. For that we can utilize the following in our pipeline:
- script: |
curl https://api.github.com/repos/$(gh_user)/$(gh_repo)/pages/builds/latest -i -v \
-X GET \
-H "Accept: application/vnd.github.mister-fantastic-preview+json" \
-H "Authorization: Basic $(gh_auth_header)"
displayName: '[GitHub] Get Page Build Status'
Conclusion
When comparing my new Azure DevOps Pipelines to the old Travis Job, I am able to say that Azure DevOps Pipelines allows me to more fine grained configure my tasks as well as deploy, really showing how strong this product is for the enterprise market.
Another noticeable thing is the performance. When deploying with Travis, my builds often took between 2 - 3 minutes, while with Azure DevOps Pipelines this takes around 1m 40s
If you're interested in the full source code, feel free to check this at: https://github.com/Xaviergeerinck/thebillkidy.github.io/blob/development/azure-pipelines.yml which is this code being used for my own personal blog ;)
Member discussion