Serverless Hosting Of A Static Page With Jekyll, CircleCI, Amazon AWS S3 And Cloudfront
As static page hosting becomes more and more popular, there should be an easy way to host your site without the hassle of keeping a full server running somewhere. For hosting some client projects and this blog, I built a pipeline with Github, CircleCI and Amazon AWS to get the static content online without the need to manage the full stack of a server including operating system, system updates and webserver config. With this how-to you can update your website with a simple git push to your underlying repository and the site will be built and updated automatically. The monthly costs for a low traffic blog will be around less than a dollar.
To get your blog running like this, do the following steps.
- Create an Amazon AWS account if you dont have one already.
- Create a S3 bucket called 'www.yourdomain.com' and go to Properties, Static Website Hosting and select 'Redirect all requests to another host name' and redirect to 'yourdomain.com'
- Create another bucket called 'yourdomain.com' and enable static website hosting. Add index.html and error.html as index and error documents.
- Create a Cloudfront distribution. Select web and use the follwoing settings:
Origin Domain Name: yourdomain.com.s3-website.<your-region>.amazonaws.com (DO NOT use what amazon offers you here as autosuggest)
Alternate Domain Names (CNAMEs): yourdomain.com
Default Root Object: index.html
Compress Objects Automatically: Yes
I would recommend to leave everything else as it is and create the Cloudfront distribution. You can change all settings later but make sure before everything is running smoothly.
- Copy your Cloudfront ID and distribution domain name.
- Create an IAM user with the following inline policy:
{
"Statement": [
{
"Action": [
"s3:DeleteObject",
"s3:GetObject",
"s3:PutObject",
"s3:PutObjectAcl"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::yourdomain.com/*"
]
},
{
"Action": [
"s3:ListBucket"
],
"Effect": "Allow",
"Resource": "arn:aws:s3:::*"
},
{
"Action": [
"cloudfront:CreateInvalidation"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
ATTENTION: AWS doesn't support resource identifiers for Cloudfront in IAM policies, which means pratically any user with this permission can invalidate all your distributions.
Create a CircleCI account and set it up with your git repository. Add the AWS credentials to it. Do not add them as ENV variables, CircleCI has its own setting for AWS credentials. Add one ENV variable with the name
AWS_CLOUDFRONT_ID
and the Cloudfront ID (not the domain!) as the value. I've chosen CircleCI because it is free for the first container and you probably will not need more to build your site, but this setup should also work with every build server environment you prefer (TravisCI, Jenkins, ...).Add the following circle.yml to your project. Don't forget to change to your bucket name and region after copy and pasting this.
machine:
python:
version: pypy-2.2.1
environment:
JEKYLL_ENV: production
dependencies:
pre:
- pip install awscli
compile:
override:
- echo "disabled"
test:
override:
- bundle exec jekyll build
deployment:
production:
branch: master
commands:
- |
aws --version
- |
aws s3 sync --region your-region --acl public-read --delete _site/ s3://yourdomain.com
- |
echo -e "\n[preview]\ncloudfront = true" >> ~/.aws/config
- |
cat ~/.aws/config
- |
aws cloudfront create-invalidation --cli-input-json '{"DistributionId":"'"$AWS_CLOUDFRONT_ID"'","InvalidationBatch":{"Paths":{"Quantity":2,"Items":["/index.html","/*"]},"CallerReference": "'$(date "+%s")'"}}'
Now push to your repo and it should build successfully on CircleCI. If you get the "Invalid date '0000-00-00': Post" Error in Jekyll's build step, be sure to exclude the vendor folder in your Jekyll conf because CircleCI is bundling directly in your working directory. Also you should add the venv folder to it, otherwise CircleCI will also upload this.
Now open the distribution domain name in your browser and you should see your website.
Now you should change your domain's DNS settings and add your cloudfront domain. I prefer using Amazon Route 53 for this job because it allows you to set also ALIAS records.
Awesome! Now you are running a super fast website and you don't need to care about anything then producing awesome content!
Some trade offs of this solution:
HTTPS is possible but expensive because Amazon has to copy your certificate to all Cloudfront edge locationsUpdate 2016-06-18: Amazon released the AWS Certificate Manager which makes SSL available through SNI for custom domains on Cloudfront at no additional costs \o/- No fast cache invalidation. You will not see your changes directly after pushing to your repository. It takes Cloudfront around 10 minutes to update your content.