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.