Static site deployment automation with CircleCI, S3, and CloudFront
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!