How to use AWS CloudFormation to setup the infrastructure for a static website
How to use AWS CloudFormation to setup the infrastructure for a static websiteBeing a software engineer by heart I am a big fan of automating any recurring manual task.
Every time we would develop a static website I would need to manually setup the whole infrastructure for it to work properly. This was a very repetitive and tedious task that we wanted to automatize.
In this article you will find our approach using AWS CloudFormation.
Our usual infrastructure architecture is as follows:
AWS - Static website infrasctructure architecture
- S3Bucket — Hosts the deployed code
- ReadPolicy & CloudFrontOriginAccessIdentity— Grants read rights to requests to the S3Bucket requests coming from Cloudfront
- CloudFrontDistribution — Handles requests to the website and retrieves the requested page from the S3Bucket
- PublishUser & PublishCredentials — IAM user with Access Keys to enable our continuous integration tools to deploy the newly created code of the website to the S3Bucket
The first time I got acquainted with AWS CloudFormation was in an AWS training program.
At the beginning of each of the practical exercises, the environment was loaded by running a stack on CloudFormation through a YAML file. I was mindblown 🤯. I immediately thought that this would allow me to automate the creation of the whole infrastructure for static website hosting.
Another very interesting feature is that every resource created via the CloudFormation stack is erased when the stack itself is deleted. This is very useful to rollback the creation of resources when we detect mistakes, saving us precious time and money.
CloudFormation syntax
As a piece of advice, before starting to set up a stack in CloudFormation, draft the needed resources in a sheet of paper 📜. You should then be prepared to jump into the CloudFormation designer.
The yaml has 3 main sections parameters, resources, and outputs:
Parameters
Parameters allow you to ask for inputs before running the stack. In the following example, we create an input parameter that will define the bucket name when creating the S3 resource.
BucketName:
Type: String
Default: 'a-proper-bucket-name'
Resources
This is where you instantiate the actual services.
In this example, we create an S3Bucket configured as a static website. Please note how we define the BucketName by using a reference to the parameter / input defined earlier.
S3Bucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Ref BucketName
WebsiteConfiguration:
ErrorDocument: 'index.html'
IndexDocument: 'index.html'
Outputs
The outputs allow you to print useful values from the created resources.
In this example, we create an output to display the S3Bucket website url.
BucketUrl:
Description: 'S3 Bucket Url'
Value: !GetAtt 'S3Bucket.WebsiteURL'
Complete example
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Static website hosting with S3 and CloudFront'
Parameters:
BucketName:
Type: String
Default: 'a-proper-bucket-name'
Resources:
# Create the bucket to contain the website HTML
S3Bucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Ref BucketName
WebsiteConfiguration:
ErrorDocument: 'index.html'
IndexDocument: 'index.html'
# Configure the bucket as a CloudFront Origin
ReadPolicy:
Type: 'AWS::S3::BucketPolicy'
Properties:
Bucket: !Ref S3Bucket
PolicyDocument:
Statement:
- Action: 's3:GetObject'
Effect: Allow
Resource: !Sub 'arn:aws:s3:::${S3Bucket}/*'
Principal: '*'
# In an ideal scenario the policy would only grant these rights to CloudFront,
# we do not do it from scratch as many projects start without having a domain name specified
# and we want to test the code as soon as possible.
# Principal: CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId
# Configure Access to CloudFroun
CloudFrontOriginAccessIdentity:
Type: 'AWS::CloudFront::CloudFrontOriginAccessIdentity'
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: !Ref S3Bucket
# Configure CloudFront
CloudFrontDistribution:
Type: 'AWS::CloudFront::Distribution'
Properties:
DistributionConfig:
CustomErrorResponses:
- ErrorCode: 403 # not found
ResponseCode: 404
ResponsePagePath: '/index.html'
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
- OPTIONS
CachedMethods:
- GET
- HEAD
- OPTIONS
Compress: true
DefaultTTL: 3600 # in seconds
ForwardedValues:
Cookies:
Forward: none
QueryString: false
MaxTTL: 86400 # in seconds
MinTTL: 60 # in seconds
TargetOriginId: s3origin
ViewerProtocolPolicy: 'allow-all'
# This DefaultRootObject configuration is not enough.
DefaultRootObject: '/index.html'
Enabled: true
HttpVersion: http2
Origins:
- DomainName: !GetAtt 'S3Bucket.DomainName'
Id: s3origin
S3OriginConfig:
OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}'
PriceClass: 'PriceClass_All'
# Create an IAM user with Access Keys to enable automated deployment of the website to this bucket
PublishUser:
Type: 'AWS::IAM::User'
Properties:
Policies:
- PolicyName: !Sub 'publish-to-${S3Bucket}'
PolicyDocument:
Statement:
- Action: 's3:*'
Effect: Allow
Resource:
- !Sub 'arn:aws:s3:::${S3Bucket}'
- !Sub 'arn:aws:s3:::${S3Bucket}/*'
PublishCredentials:
Type: 'AWS::IAM::AccessKey'
Properties:
UserName: !Ref PublishUser
Outputs:
Bucket:
Description: 'S3 Bucket Name'
Value: !Ref S3Bucket
BucketUrl:
Description: 'S3 Bucket Url'
Value: !GetAtt 'S3Bucket.WebsiteURL'
AccessKeyId:
Description: 'S3 Access Key'
Value: !Ref PublishCredentials
AccessKeySecret:
Description: 'S3 Secret Key'
Value: !GetAtt PublishCredentials.SecretAccessKey
DistributionId:
Description: 'CloudFront Distribution ID'
Value: !Ref CloudFrontDistribution
Domain:
Description: 'Cloudfront Domain'
Value: !GetAtt CloudFrontDistribution.DomainName
Execution
Now it’s time to benefit from the blueprint created.
First, you need to create a stack, filling in the inputs required by the parameters and then execute it:
AWS Cloudformation - Create stack snapshot
You can now grab a ☕️ as the execution might take some time until it’s complete. You can check the status of the creation at any time:
AWS Cloudformation - create stack progress snapshot
⚠️ Please keep in mind that when you delete the stack you also remove all the resources created. This is usefull for testing but you might make the mistake (like we did) of deleting the stack after all the resources are created.
Useful links
- Cloudformation official documentation
- Automate Your Static Hosting Environment With AWS CloudFormation blog post from Forestry
Thank you for reading!
Thank you so much for reading, it means a lot to us! Also don’t forget to follow Coletiv on Twitter and LinkedIn as we keep posting more and more interesting articles on multiple technologies.
In case you don’t know, Coletiv is a software development studio from Porto specialised in Elixir, Web, and App (iOS & Android) development. But we do all kinds of stuff. We take care of UX/UI design, software development, and even security for you.