For people who want to send their thoughts out into the world, a blog is the perfect medium of delivery. Although the maintenance of a blog feels easy in the beginning, as the content grows with time it becomes difficult to manage the process manually. When the blog hits critical mass, it becomes important to look at automating the maintenance process.
This is where CI/CD tools come in. These tools will automate the testing and deployment stages in a fast and efficient manner.
Before we get into the nitty-gritty details, let me give you my use-case. My blog is a static site built using Jekyll. After adding content for over a year, the blog has grown large enough that changing one thing may break something else. For testing my builds, below are the high-level steps I wanted automated.
- On every commit to the
masterbranch, take the code hosted on my GitHub repository (repo in short).
- Run sanity checks to see there are no breaking changes.
- Deploy to my web server - hosted on a Linux hosted server.
The repetitive nature of this task made me realize that my blog had grown to a point where using a CI/CD pipeline started to make sense.
What is CI/CD?
Continuous Integration and Continuous Delivery and/or Continuous Deployment is the practice of continuously merging all the developers’ code into a single codebase; for automated building, testing, and quicker delivery/deployment. In simple language, the code written by everyone in the team resides in a single place and is always delivery-ready, by running self-testing automated builds on each code check-in.
The CI/CD process places great importance on releasing software frequently. This will, in turn, lead to faster updates for the product, and a better experience for the user. Here is a good CI/CD explainer from Atlassian.
Continuous Delivery ensures that the latest code is ready for deployment, by incorporating all the changes successfully up to the present time (e.g., Minor/Major Version releases).
If Continuous Deployment is part of the CI/CD pipeline, then the code is deployed automatically in short cycles (e.g., nightlies).
CI/CD for individuals/blog-owners
I agree that the complete set of advantages offered by a dedicated CI/CD pipeline is a bit of overkill for us mere mortals. But then, all of us deserve a bit more of automation in our lives, don’t we?! With this spirit, I came up with the 3 steps that I would like to automate :)
A bit of googling (is this still a thing?!) revealed that a CI/CD pipeline using containers is the fastest way to automate the above requirement. Out of the various options available (the popular ones being Jenkins, Travis CI, CircleCI, Buddy, etc..), I zeroed in on CircleCI. CircleCI is currently offering free accounts for GitHub repo owners, as are a few others listed above.
Using the CircleCI platform, my requirement given above can be automated as follows:
- Create a separate
jobin the CircleCI pipeline to accomplish the step.
- Chain the 3
jobs into a
workflowso that there is a logical flow. Say
Job 3. You may also specify the dependency between steps in the workflow, something like
Job 2needs to wait for successful completion of
Job 1. Or, that
Job 3will wait for
Job 2to complete.
- Specify the
triggercondition for your workflow.
A simple CircleCI Job
version: 2.1 jobs: build: docker: - image: circleci/node:4.8.2 # the primary container, where your job's commands are run steps: - checkout # check out the code in the project directory - run: echo "hello world" # run the `echo` command
The above CircleCI configuration (saved in a CircleCI
config.yml file) lists a job called “build” which has 3 steps:
- It spins up a docker image for
node.jsplatform. Think of this step as bringing up the
node.jsenvironment for program execution.
- Checks out code - from a GitHub repository that you’ve connected previously.
- Runs echo command inside the docker image.
A Docker image is a container that gives the required functionality/environment for running your code. For your project, the container will provide an easy way to install dependencies and run your code in an isolated environment. A container is not fully featured like a VM, but faster to bring up and run code. So it works great for testing individual code components!
A more complete example
jobs: build: docker: - image: circleci/<language>:<version TAG> steps: - checkout - run: <command> test: docker: - image: circleci/<language>:<version TAG> steps: - checkout - run: <command> deploy: docker: - image: circleci/<language>:<version TAG> steps: - checkout - run: <command> workflows: version: 2 build_and_test: jobs: - build - test - deploy: requires: - build - test
Now that you have seen jobs in action, it’s time to understand the next level:
workflows. A workflow allows you to define the logical sequence of the individual jobs, complete with dependencies between each. In the above example, the
test jobs run in parallel and once both of them complete successfully, the
deploy job is run.
Use CircleCI to push changes to your blog
If your website code resides on GitHub, you can connect it to CircleCI and start watching your GitHub repo for commits.
The only thing required is to add the below file to your repo.
config.yml file contains the configuration required by
CircleCI for the jobs/workflows to be run! And you can keep the
config.yml as part of your build. Pretty nifty, right?!
Next up, is to build your website inside the docker image. My website is built on
Jekyll in a
Ruby environment. So, I will launch a ruby docker image and build my website in it.
build job is given below
jobs: build: <<: *defaults docker: - image: circleci/ruby:2.5 environment: BUNDLE_PATH: ~/repo/vendor/bundle steps: - checkout - run: name: Bundle Install command: bundle check || bundle install - run: name: Jekyll build command: bundle exec jekyll build
The next step is to run a sanity check on the website, to check if it’s running fine. HTMLProofer checks to see if your rendered website works as intended, and that’s what I use. After this, we will save the website (in the
_site folder) to be used for deploying in the next job.
- run: name: HTMLProofer tests command: | bundle exec htmlproofer ./_site \ --allow-hash-href \ --check-favicon \ --check-html \ --disable-external - persist_to_workspace: root: ./ paths: - _site
deploy job will first get the data saved from the previous job. Then, it uses the rsync Linux command to sync the
_site folder contents to the web server.
rsync command does the following:
- –delete flag: delete the contents in the Destination folder that doesn’t match the contents of Source folder (
- –exclude flag: excludes the file/folder on the Destination side from deletion.
- The file/folders to be copied, or only copied if they’ve been modified in any way. And only the modified part is copied, not the entire file!
deploy: machine: enabled: true steps: - attach_workspace: at: ./ - run: name: Deploy _site Over SSH command: | rsync -a --progress --delete _site/ email@example.com:/DEST/public_html/ --exclude=/.git/ --exclude=/cgi-bin/ --exclude=/.gitignore
Well, that’s it! Now, make some changes and commit them to your GitHub repo. And watch the CircleCI workflow take over. It took me just a day (and a bit of night :) ) to automate this. Who knows, you may do it in a shorter time.
Even though I have used CircleCI here, you can use any other CI/CD solution that is out there. It will follow a similar approach to what is described here. Go ahead and dabble a bit!