Setting up CI/CD for Jekyll with CircleCI and AWS S3/CloudFront
This document is based on an older version of CircleCI, configurations in this document will need to be updated to leverage CircleCI 2.0 to work properly.
Getting Started
There are many options for CI, in this example, I’m going to use CircleCI. You get one container for free, the containers it builds with come with awscli already installed, and it’s APIs are very featureful. Since it’s free as in beer, and we don’t have to host our own CI service, like Jenkins or Drone, this is a winner for our purposes.
What is CI and CD?
ThoughtWorks has some some very great articles on Continuous Integration and Continuous Delivery. I recommend reading those articles for more details on what they are. A cliff notes version of this is, commit your code in regularly, and make sure it pass tests. Once everything is in and passing, automatically deploy your application. This pipeline is critical to repeatable deployments. You should absolutely do these things.
AWS
This example assumes you’ve already setup your S3 bucket and CloudFront to serve your site.
Create an IAM policy for CD
The below policy will allow your CD user to deploy your site to your S3 bucket, as well as invalidate your CloudFront cache. You will notice the resources for S3 are very specific, however the CloudFront resource is not. CloudFront does not allow you to specify which distribution to allow actions on. Replace bucket.name
in the following policy with the name of your S3 bucket:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:List*",
"s3:PutObject",
"s3:DeleteObject"
],
"Effect": "Allow",
"Resource": "arn:aws:s3:::bucket.name/*"
},
{
"Action": [
"s3:List*"
],
"Effect": "Allow",
"Resource": "arn:aws:s3:::bucket.name"
},
{
"Effect": "Allow",
"Action": [
"cloudfront:CreateInvalidation"
],
"Resource": "*"
}
]
}
Create your CD user
Create a user for CD use. I generally make the name descriptive so it’s easy to see what it’s used for. At the end of the user creation process, you’ll be given the user’s Access Key ID and Secret Access Key. Save both of these values for later when you’re setting up CircleCI.
Once you create your user, attach your newly created IAM policy for CD to this user.
CircleCI
Follow the Getting started with CircleCI docs to login with your GitHub credentials. Once logged in, use the “Add Projects” button to go find your repo that you want to build, then click “Build Project”. As a disclaimer, there’s a very good chance your first build will fail for any number of reasons. That’s not a big deal, and we’ll cover the rest of the setup in here.
Setup AWS Permissions
Once you’ve opted to “Build Project”, go to the project settings page to setup your AWS permissions. You will use the Access Key ID and Secret Access Key that you received while setting up your AWS user.
Setup Additional Environment Variables
I highly recommend using environment variables so that your circle.yml
which we’ll create in the next step is more portable. In this case we will create S3_BUCKET
and CLOUDFRONT_DISTRIBUTION_ID
variables that reflect our S3 bucket name, and our CloudFront distribution ID.
circle.yml
In the root of your repo, you should include a file called circle.yml
. The example I’m about to show assumes you’ve been following along with the previous posts about building your Jekyll blog. You may need to make some adjustments based on the decisions you’ve made on how to build your application. Here’s a complete circle.yml
that uses environment variables from the previous step to figure out where to deploy.
machine:
ruby:
version: 2.2.3
dependencies:
pre:
- aws configure set region us-east-1
- aws configure set preview.cloudfront true
- aws configure set preview.create-invalidation true
override:
- bundle install --path=~/.bundle --jobs=4 --retry=3:
timeout: 600
test:
override:
- cd $HOME/$CIRCLE_PROJECT_REPONAME && JEKYLL_ENV=production bundle exec rake build
- cd $HOME/$CIRCLE_PROJECT_REPONAME && JEKYLL_ENV=production bundle exec rake html_proofer
deployment:
production:
branch: master
commands:
- cd $HOME/$CIRCLE_PROJECT_REPONAME/_site && aws s3 sync --delete ./ s3://$S3_BUCKET/
- aws cloudfront create-invalidation --cli-input-json "{\"DistributionId\":\"$CLOUDFRONT_DISTRIBUTION_ID\",\"InvalidationBatch\":{\"Paths\":{\"Quantity\":1,\"Items\":[\"/*\"]},\"CallerReference\":\"$(date +%s)\"}}"
The dependency section outlines how we’re configuring the awscli tool to work with the region we have our S3 bucket in, and it also enables the preview features of CloudFront that we need in order to create invalidations. Lastly, we install everything from our Gemfile, assuming we have one.
The tests reflect what we’ve built within the article called Some Jekyll Tests. All of the environment variables that start with $CIRCLE
are provided during the build from CircleCI.
Lastly, the deployment step can be setup on a per branch basis. Depending on your git workflow, you may want to deploy to different environments base don branch. In this example, we are only deploying master. For the S3 deployment, it will go into your site directory and push up your site to the S3 bucket you’ve setup. And then CircleCI will issue an invalidation for your CloudFront distribution. The CloudFront step may look ugly, but it is what’s needed to issue the invalidation. If you get fancy, you can have your CD job verify what has changed as a part of the deployment and only invalidate the objects which have changed.
Now push a change to make sure everything you’ve done works. It’s not uncommon to have it fail through the first few builds as you tweak tests and permissions to work in your CI environment.