Static site hosting with Cloudfront and S3
This is a story all about how I migrated my old DokuWiki to stefan.midjich.name and MKDocs using Cloudfront and S3.
Step 1: Create a bucket in S3
This is an easy first step that can get you started with creating a deploy pipeline, as I did in my case. Just create a bucket with public access and static site hosting. Test it using the S3 generated URI.
Then you can finish your deploy pipeline and test that too before proceeding. An example of mine is in the git repo of my site.
Step 2: Create a domain in Route53
I setup a new hosted zone in Route53, this is pretty self-explanatory. In my case it was midjich.name, no sub-domain yet because we need a CloudFront distribution first.
I'm creating the domain first because step 3 requires you to authenticate with either a CNAME record or e-mail and it's just much easier to authenticate with CNAME. AWS has that fully integrated into the UI so you just point and click.
Step 3: Create a TLS certificate in AWS Certificate Manager
This is good to get out of the way before creating a Cloudfront distribution because then you can select the certificate during the Cloudfront creation process.
The process is pretty self-explanatory, I used the domain stefan.midjich.name and chose DNS verification because then I can just allow AWS to create their own CNAME record and verify ownership of the domain.
Remember to use the region us-east-1 otherwise the TLS cert won't be available to your Cloudfront distribution. This probably has something to do with Cloudfront being a Global service but I am not sure.
Step 4: Create a Cloudfront distribution
Cloudfront is required if you want to use your own domain and TLS, it sits infront of your S3 back end and terminates TLS. It also makes requests for objects in S3.
I use mostly default settings but here's a list of what I change during Cloudfront distribution creation.
- Origin Domain Name - Use your S3 website endpoint here, it's bucketname.s3-website.region-1.amazonaws.com (or in my case stefan.midjich.name.s3-website.eu-north-1.amazonaws.com). You can find your website endpoint name as a URL in the S3 bucket Properties under Static website hosting.
- Compress Objects Automatically - Yes
- Alternate Domain Names - Enter your full domain here, in my case stefan.midjich.name.
- Default Root Object -
index.html
in my case, this is only to find the index file of the root / path. - Custom SSL Certificate - Your cert should appear here automatically if you use the form input field.
Warning
Cloudfront is as of writing unable to handle sub-directory indexes, unless you enter your S3 address as I do here. If you use the AWS provided "REST" endpoint of your S3 bucket, that AWS provides you in their dropdown list, you must create a lambda trigger function as described here.
Step 5: Point your domain to Cloudfront
Now you create the domain record in Route53 that points to Cloudfront. In my case it's a subdomain but that doesn't matter. This is fairly simple because you select Alias and can find the Cloudfront distribution in the dropdown list.
Deployment pipeline
Here are some more things I do after the site is setup, for deployment.
Create a deployment user in IAM
I use the same user to deploy the site to S3 and invalidate its Cloudfront cache so changes are visible immediately.
- Create a new user in IAM.
- Give it programmatic access.
- Give it no policies at this point, just create a "blank" user.
- Name it something specific to your site, do not re-use users for multiple sites to avoid security issues affecting all your sites.
- After creating the user attach an inline JSON policy to it, see below for policy JSON.
- You can find the Cloudfront arn if you click your Cloudfront distribution in the AWS web gui and look under ARN.
- Make the secret key ID and secret key available to your deployment pipeline, in my case I use Gitlab.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "MyWebsiteDeploymentPolicy",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket",
"s3:DeleteObject",
"s3:PutObjectAcl",
"s3:GetObjectAcl"
],
"Resource": [
"arn:aws:s3:::mywebsite",
"arn:aws:s3:::mywebsite/*"
]
},
{
"Action": [
"cloudfront:CreateInvalidation"
],
"Effect": "Allow",
"Resource": "arn:aws:cloudfront::1234xxxxx:distribution/ABCD1234xxxx"
}
]
}