Static site deployment automation with CircleCI, S3, and CloudFront

That was fast!

How to automate testing, compiling, and deploying a static site to AWS S3 and CloudFront using CircleCI.

Some problems are more difficult to solve than others. Fortunately compiling, deploying, and serving a static website is an easy problem to solve.

You could run your static website on servers or virtual machines. Or, you can just deploy your static content to an AWS S3 bucket and let AWS handle the computing and web serving.

There are many ways to automate this using Jenkins, CircleCI, and similar tools. In this example, we’re going to show you how to do this with CircleCI.

We’re using harpjs “The static web server with built-in preprocessing” to compile our static site.

Create S3 and CloudFront resources

Create an S3 bucket

Create a CloudFront Distribution

Create an IAM user

Create an IAM user with the appropriate IAM policy access to S3 and CloudFront. We’ll interact with S3 and CloudFront using this IAM user.

Here’s an example IAM policy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:List*"
            ],
            "Resource": "arn:aws:s3:::*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": "arn:aws:s3:::example.com"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": "arn:aws:s3:::example.com/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": "arn:aws:s3:::staging.example.com"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": "arn:aws:s3:::staging.example.com/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": "arn:aws:s3:::acceptance.example.com"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": "arn:aws:s3:::acceptance.example.com/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "cloudfront:CreateInvalidation"
            ],
            "Resource": "*"
        }
    ]
}

Configure CircleCI

Configure CircleCI with your website’s repository and appropriate AWS credentials.

Configure Circle.yml

Configure circle.yml

npm run compile simply runs the command harp compile

We’re invalidating all of the objects in the distribution with the wildcard *

The CallerReference value has to be unique each time you create a CloudFront cache invalidation. We create a unique value each time by executing the date command.

Here’s an example circle.yml file
machine:
  timezone:
    America/Los_Angeles
  node:
    version: v0.10.34
dependencies:
  override:
    - npm install
  post:
    - sudo pip install awscli
    - aws configure set region us-west-2
    - aws configure set preview.cloudfront true
    - aws configure set preview.create-invalidation true
test:
  override:
    - npm test
deployment:
  acceptance:
    branch: acceptance
    commands:
      - npm run compile
      - aws s3 sync --delete www/ s3://acceptance.example.com/
  staging:
    branch: staging
    commands:
      - npm run compile
      - aws s3 sync --delete www/ s3://staging.example.com/
  production:
    branch: production
    commands:
      - npm run compile
      - aws s3 sync --delete www/ s3://example.com/
      - aws cloudfront create-invalidation --cli-input-json "{\"DistributionId\":\"ABCDEFGHIJKLMNOP\",\"InvalidationBatch\":{\"Paths\":{\"Quantity\":1,\"Items\":[\"/*\"]},\"CallerReference\":\"$(date +%s)\"}}"

Troubleshooting

If you have any trouble try to simplify things, for instance, by running a CircleCI SSH build and running the commands from within the CircleCI container or perhaps just running the commands from your laptop. You might run into issues with IAM policies, awscli configuration, or command quoting.

Done

That was fun!