
15 Aug 2019 Deploying a Static Web Application with Serverless and CodeBuild
This blog post is a tutorial on how to deploy a static web application to AWS S3.
It includes a comprehensive explanation on what we’re doing; hence the length! However, there will be some skipping points which you can click when you don’t want to hear about lengthy explanations.
For you who like to dive into the source code directly, I’ve made a Github repo for this post as well.
I present to you the first skipping point. It will escort you to the Architecture Overview without having to deal with the reasoning behind this post.
Why are we doing this?
First of all, I like to save money; and S3 has a great pricing scheme. There are some other advantages, though.
However, it does not support back-end codes such as Java, Go, Python, etc. It’s a “Simple Storage Service”.
This post will use one of the most popular frameworks: ReactJS.
But you don’t need to use React specifically. If you have any preferred frameworks like VueJS, Angular, or even plain HTML; you can use S3 too. As long as it produces static sites.
Hold on, how do I interact with the database then?
Usually, the website would interact with an API(s) which is hosted somewhere else. This API(s) will interact with the database. One of the most popular options in building serverless API(s) can be achieved by using AWS Lambda. However, I won’t talk about that in this post.
What can I expect from this post?
I will cover how to deploy a simple ReactJS application (“Hello World!”) to an S3 Bucket which can later be accessed through our browser. It includes:
- Hosting in S3;
- Using CloudFront as a CDN;
- Securing our S3 bucket;
- Using a custom domain for your CloudFront distribution (optional);
- One-step deployment using CodeBuild (optional);
- Doing all of them using Serverless Framework;
However, if you’re expecting a ReactJS tutorial, this is not the one.
Now we’re in business!
Architecture Overview
Before we dive in, take a look at the architecture overview below.

From the image above, we can tell that when a user wants to access our React application, they will go through CloudFront which will access our React application in an S3 Bucket.
AWS CloudFront is a CDN (Content Delivery Network) from AWS. Think of it like your favourite restaurant opening a branch near your house. It brings the resources closer to you! CDN won’t serve food, but it will serve you web contents (images/videos/any static files).
An S3 Bucket is the origin of those resources. Think about the main branch of your favourite restaurants. It may not always be the case, but imagine that the “new” branches require supplies for the food; and those supplies will be provided from the “main” branch. Anyhow, it’s where you have to upload your React application.
Furthermore, CloudFront will protect our wallet by preventing abusive GET requests to our S3 Bucket (we need to pay per GET request!).
You’ve encountered the second skipping point. It will escort you to the end of this post! It’s great if you just need the solution rather than the explanation.
Serverless Framework Initialisation
You can entirely skip this if you’re familiar with Serverless Framework.
The first step of our initialisation is to configure our machine to access AWS resources. You need to inject AWS credentials into your computer. Make sure that you have configured your credentials
file.
The second step is to create a serverless.yml
file. This is a manual process. The hardest part of this step is to determine where would you put the file. I like to put it within the React project itself. So it’d be something like this:

Let’s write our initial configuration in the serverless.yml
.
service:
name: example-app
provider:
name: aws
region: ap-southeast-2
These 2 blocks are something you need to do for every serverless project.
service
is your service name. This will be reflected in your CloudFormation Stack.
The provider section tells the framework that you’re using aws
and you’re using ap-southeast-2
(Sydney) as your AWS region.
Orchestrating Our Resources
To help you navigate through this post, these are the steps that we’d take:
- Creating an S3 Bucket;
- Creating a CloudFront Distribution;
- Securing our S3 Bucket;
- Using a custom domain for our CloudFront Distribution (optional);
- Creating a “one-step” deployment with CodeBuild (optional);
And below is the final configuration. Feel free to just read this configuration and ignore the explanations. Note that this configuration does not use a custom domain.
# the previous `service` and `provider` block
resources:
Resources:
FrontPageWebsiteBucket:
Type: AWS::S3::Bucket
FrontPageWebsiteBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref FrontPageWebsiteBucket
PolicyDocument:
Statement:
- Effect: Allow
Action:
- s3:GetObject
Resource:
Fn::Join:
- /
- - Fn::GetAtt:
- FrontPageWebsiteBucket
- Arn
- '*'
Principal:
CanonicalUser:
Fn::GetAtt:
- FrontPageWebsiteOriginAccessIdentity
- S3CanonicalUserId
FrontPageWebsiteOriginAccessIdentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: Origin Access Identity to Access Website Bucket
FrontPageCloudFront:
Type: AWS::CloudFront::Distribution
DependsOn:
- FrontPageWebsiteBucket
Properties:
DistributionConfig:
Origins:
- DomainName:
Fn::GetAtt:
- FrontPageWebsiteBucket
- DomainName
Id: S3Origin
S3OriginConfig:
OriginAccessIdentity:
Fn::Join:
- /
- - origin-access-identity
- cloudfront
- !Ref FrontPageWebsiteOriginAccessIdentity
CustomErrorResponses:
- ErrorCachingMinTTL: 0
ErrorCode: 403
ResponseCode: 200
ResponsePagePath: /index.html
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
Compress: true
ForwardedValues:
QueryString: true
Cookies:
Forward: none
TargetOriginId: S3Origin
ViewerProtocolPolicy: redirect-to-https
Comment: my example website in s3
DefaultRootObject: index.html
Enabled: true
HttpVersion: http2
PriceClass: PriceClass_All
ViewerCertificate:
CloudFrontDefaultCertificate: true
This is the third skipping point. It will escort you to the end of this post! Or you can stop here if you like. The rest of this post will explain the content of our
serverless.yml
.
1. Creating an S3 Bucket
This is very straightforward. Take a look at the configuration for creating an S3 bucket below:
FrontPageWebsiteBucket:
Type: AWS::S3::Bucket
That’s it!
2. Creating a CloudFront Distribution
This is a bit complicated. But I’ll try to explain it.
FrontPageCloudFront:
Type: AWS::CloudFront::Distribution
DependsOn:
- FrontPageWebsiteBucket
Properties:
DistributionConfig:
Origins:
- DomainName:
Fn::GetAtt:
- FrontPageWebsiteBucket
- DomainName
Id: S3Origin
S3OriginConfig:
OriginAccessIdentity:
Fn::Join:
- /
- - origin-access-identity
- cloudfront
- !Ref FrontPageWebsiteOriginAccessIdentity
CustomErrorResponses:
- ErrorCachingMinTTL: 0
ErrorCode: 403
ResponseCode: 200
ResponsePagePath: /index.html
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
Compress: true
ForwardedValues:
QueryString: true
Cookies:
Forward: none
TargetOriginId: S3Origin
ViewerProtocolPolicy: redirect-to-https
Comment: my example website in s3
DefaultRootObject: index.html
Enabled: true
ViewerCertificate:
CloudFrontDefaultCertificate: true
Ugh.. It’s really long isn’t it.
Let’s break it down.
Origins
This is essentially telling our CloudFront distribution to retrieve files from the S3 Bucket that we’ve created before.
Origins:
- DomainName:
Fn::GetAtt:
- FrontPageWebsiteBucket
- DomainName
Id: S3Origin
S3OriginConfig:
OriginAccessIdentity:
Fn::Join:
- /
- - origin-access-identity
- cloudfront
- !Ref FrontPageWebsiteOriginAccessIdentity
Ignore the OriginAccessIdentity
for now. It’ll be used to secure our S3 Bucket.
CustomErrorResponses
This is applicable mostly for SPA (e.g. React/Angular) as we want to handle routes in our application. However, if you’re using HTML, you don’t need to do this.
CustomErrorResponses:
- ErrorCachingMinTTL: 0
ErrorCode: 403
ResponseCode: 200
ResponsePagePath: /index.html
Any 403 (Forbidden) Errors will be redirected to our index.html
. This is essential as when we access routes (e.g. /home
or /about
), most SPA doesn’t have the corresponding folder for the routes; hence the 403.
DefaultCacheBehavior
This is where we can optimise our delivery by compressing (gzipping) our static files, forwarding query strings to our app, redirecting HTTP to HTTPS, and only allowing certain HTTP methods.
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
Compress: true
ForwardedValues:
QueryString: true
Cookies:
Forward: none
TargetOriginId: S3Origin
ViewerProtocolPolicy: redirect-to-https
Note the TargetOriginId
must match with Id
in Origins
.
ViewerCertificate
This is used for specifying the SSL certificate for our distribution. As we’re using CloudFront’s domain name, we can just use the default certificate from CloudFront.
ViewerCertificate:
CloudFrontDefaultCertificate: true
Other Configs
Comment: my example website in s3
DefaultRootObject: index.html
Enabled: true
Comment
is optional. This is used for making it easy to identify your distribution in AWS console.
DefaultRootObject
is the default file you want to access. In our case, it’s index.html.
Enabled
enables the distribution (duh!). Although seems unnecessary, this is required.
3. Securing S3 Bucket
I know it’s a vague heading as security is a really broad topic. This post will only focus on securing your S3 bucket from abusive requests.
Because you’ve created your CloudFront distribution, you don’t want people to access your S3 bucket directly. Imagine a person issuing thousands of GET requests to your S3 bucket per second. Your wallet will cry.
The concept is pretty simple. You only want your CloudFront distribution to access your S3 bucket. No one else (except authorised users like you).
The first step is to create an identity for your CloudFront distribution. Remember OriginAccessIdentity
in the previous section?
These are 2 pieces that you need to secure your bucket.
FrontPageWebsiteBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref FrontPageWebsiteBucket
PolicyDocument:
Statement:
- Effect: Allow
Action:
- s3:GetObject
Resource:
Fn::Join:
- /
- - Fn::GetAtt:
- FrontPageWebsiteBucket
- Arn
- '*'
Principal:
CanonicalUser:
Fn::GetAtt:
- FrontPageWebsiteOriginAccessIdentity
- S3CanonicalUserId
FrontPageWebsiteOriginAccessIdentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: Origin Access Identity to Access Website Bucket
The first block is a policy for our S3 bucket. Essentially, it tells our bucket that it should whitelist our CloudFront.
The second block is the identity of our CloudFront. Nothing much in there; but you need to reference that block in your CloudFront origin.
Below is how you attach it. (this is just a copy-paste from the previous section).
Origins:
- DomainName:
Fn::GetAtt:
- FrontPageWebsiteBucket
- DomainName
Id: S3Origin
S3OriginConfig:
OriginAccessIdentity:
Fn::Join:
- /
- - origin-access-identity
- cloudfront
- !Ref FrontPageWebsiteOriginAccessIdentity
4. Using a custom domain for CloudFront distribution (optional)
This step is optional and requires you to have a custom domain.
Therefore, I present you with the fourth skipping point. It will take you to the end of this post. You can go to Step 5: One-step deployment using CodeBuild too if you like.
Let’s be honest. Even though CloudFront’s domain name is free, it’s still ugly.
Prerequisite
To be able to use your domain name in your CloudFront distribution, you need to point the domain to CloudFront’s ugly domain.
You can use another company to manage your DNS record and use your custom SSL certificate. However, I’m going to use Route53 (DNS) and an SSL certificate issued from AWS Certificate Manager.
You don’t have to use them, but I’ll be using them in this post.
Generating SSL Certificate in AWS Certificate Manager
I think the tutorial from AWS is clear enough.
The certificate is free when you use them with CloudFront.
Note that you need to create the certificate in us-east-1 region. Otherwise, CloudFront won’t pick up the certificate.
Registering your domain name in Route53
If you haven’t bought the domain, AWS has provided a straightforward tutorial to register domain names with them.
If you already have a domain name and want to move the records to Route53, you can follow yet another AWS tutorial.
Now all you need to do is to create a Hosted Zone
in Route53 for your domain. AWS should automatically create one for you when you register your domain with them. However, if for some reason you’re not seeing your domain in the hosted zone list, you can always make a new one.
Now we’re ready.
First of all, this is the final serverless.yml
file:
# the previous `service` and `provider` block
resources:
Resources:
FrontPageWebsiteBucket:
Type: AWS::S3::Bucket
FrontPageWebsiteBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref FrontPageWebsiteBucket
PolicyDocument:
Statement:
- Effect: Allow
Action:
- s3:GetObject
Resource:
Fn::Join:
- /
- - Fn::GetAtt:
- FrontPageWebsiteBucket
- Arn
- '*'
Principal:
CanonicalUser:
Fn::GetAtt:
- FrontPageWebsiteOriginAccessIdentity
- S3CanonicalUserId
FrontPageWebsiteOriginAccessIdentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: Origin Access Identity to Access Website Bucket
FrontPageCloudFront:
Type: AWS::CloudFront::Distribution
DependsOn:
- FrontPageWebsiteBucket
Properties:
DistributionConfig:
Origins:
- DomainName:
Fn::GetAtt:
- FrontPageWebsiteBucket
- DomainName
Id: S3Origin
S3OriginConfig:
OriginAccessIdentity:
Fn::Join:
- /
- - origin-access-identity
- cloudfront
- !Ref FrontPageWebsiteOriginAccessIdentity
CustomErrorResponses:
- ErrorCachingMinTTL: 0
ErrorCode: 403
ResponseCode: 200
ResponsePagePath: /index.html
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
Compress: true
ForwardedValues:
QueryString: true
Cookies:
Forward: none
TargetOriginId: S3Origin
ViewerProtocolPolicy: redirect-to-https
Comment: my example website in s3
DefaultRootObject: index.html
Enabled: true
HttpVersion: http2
PriceClass: PriceClass_All
ViewerCertificate:
AcmCertificateArn: arn:aws:acm:us-east-1:....
MinimumProtocolVersion: TLSv1.1_2016
SslSupportMethod: sni-only
Aliases:
- example.com
FrontPageDNSName:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneName: example.com.
RecordSets:
- Name: example.com
Type: A
AliasTarget:
HostedZoneId: Z2FDTNDATAQYW2 #cloudfront hostedzone id
DNSName:
Fn::GetAtt:
- FrontPageCloudFront
- DomainName
This is a modified version from our previous serverless.yml
.
The fifth skipping point. It will take you to the end of this post. You can go to Step 5: One-step deployment using CodeBuild too if you like. The rest of this section is just explaining what we’ve changed in the
serverless.yml
.
Creating a DNS record in your HostedZone
We will create a record in our hosted zone.
If you’re not using Route53, skip this bit.
FrontPageDNSName:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneName: example.com.
RecordSets:
- Name: example.com
Type: A
AliasTarget:
HostedZoneId: Z2FDTNDATAQYW2 #cloudfront hostedzone id
DNSName:
Fn::GetAtt:
- FrontPageCloudFront
- DomainName
That configuration will create a DNS record (example.com) and point it to our CloudFront domain. You might notice that there’s a hard-coded value in the HostedZoneId
. It’s a static value which you can use as well. But you need proof.
Note that the HostedZoneName
is not the same as Name
in RecordSets
. HostedZoneName is the”root” domain. The Name in RecordSets is the domain name you want for your CloudFront distribution.
Example: You want to point website.example.com
to your CloudFront distribution. HostedZoneName
would be example.com.
and the Name
in RecordSets
would be website.example.com
.
Registering your domain name in CloudFront
The next thing you need to do is to let CloudFront know that you will be using your custom domain name. This applies to whether you’re using Route53 or not.
ViewerCertificate:
AcmCertificateArn: arn:aws:acm:us-east-1:....
MinimumProtocolVersion: TLSv1.1_2016
SslSupportMethod: sni-only
Aliases:
- example.com
We have to change the certificate to a custom one as CloudFront’s default certificate only supports CloudFront’s domain name. In this case, I’m using my previously created certificate from AWS Certificate Manager.
We also have to add our domain name to the Aliases
block. This is not the HostedZoneName
. If you want to point a subdomain (website.example.com), use the subdomain instead of the “root” domain name.
5. One-step deployment using CodeBuild (optional)
To be honest, I hesitated to include this in this post. There are so many ways to deploy your static application that you may find this section useless.
However, for the sake of completeness, I’m going to provide you a simple deployment method that can be useful in certain situations.
The goals are:
- Easy & fast deployment;
- Execute certain commands before/after deploying (testing, notification, etc);
However, the method in this post will have some limitations such as:
- The “one-step” mentioned is a manual process. It’s not automated through git;
- Does not support complex pipeline (e.g. manual approval);
Time for another skipping point. It will guide you to the end of this post.
Let’s start.
First: if you haven’t done this, you need to put your project to Github / Bitbucket / CodeCommit. CodeBuild needs to retrieve your project from somewhere.
Second: if you’re using private repositories on Github / Bitbucket, you need to allow CodeBuild to access your repository. You need to connect your AWS account to your Github / Bitbucket account. You can do this by attempting to create a build project in AWS Console
> CodeBuild
> Create Project
. In the Source
section, pick Github / Bitbucket, then there will be an option to connect your Github / Bitbucket account to CodeBuild. After connecting, you can just close the tab. You don’t need to create the project for now.
Third: Create a CodeBuild project (we’ll focus on this from now on).
Creating the project
We’re going to use Serverless Framework to do this as well. This will be a different project from our previous attempt on creating S3 + CloudFront.
We need to create another serverless.yml
file in a different folder. I like to create a __deploy
folder in the React project and put the file there. However, it’s only a matter of preference.

Now, let’s see what’s inside our serverless.yml
.
service:
name: example-app-deploy
provider:
name: aws
region: ap-southeast-2
resources:
Resources:
CodeBuildRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- codebuild.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: ${self:service.name}-build-policy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:*
Resource:
Fn::Join:
- ':'
- - arn:aws:logs
- !Ref AWS::Region
- !Ref AWS::AccountId
- log-group
- /aws/codebuild/example-app-deploy
- '*'
- '*'
- Effect: Allow
Action: s3:PutObject
Resource:
Fn::Join:
- ':'
- - arn:aws:s3
- ''
- ''
- example-app-dev-frontpagewebsitebucket-19jr14sozhbv6/*
- Effect: Allow
Action: cloudfront:CreateInvalidation
Resource:
Fn::Join:
- ':'
- - arn:aws:cloudfront
- ''
- !Ref AWS::AccountId
- distribution/E12MWEG8DY8FNG
CodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Name: ${self:service.name}
Description: My website builder
ServiceRole: !Ref CodeBuildRole
Artifacts:
Type: NO_ARTIFACTS
Environment:
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/standard:2.0
Type: LINUX_CONTAINER
Source:
Location: https://github.com/kkesley/react-s3-blog-example.git
Type: GITHUB
There are 2 main resources created here:
- The IAM role for our CodeBuild project
- The CodeBuild project itself
The role configuration is long but straightforward. When you read it, you’ll notice that we’re giving the role 3 permissions: Writing build logs to a specific CloudWatch group, uploading files to an S3 bucket, and creating invalidations to our CloudFront distribution. You need to change the Resource
value as yours won’t be the same as mine.
Next is the build project itself. The only thing you need to change is the git URL in Location
and possibly the Type
if you’re not using Github.
Note that this template is far from ideal. Ideally, you’d want those hardcoded resources to be dynamic. You can use the output
block from CloudFormation to fill those hardcoded resources. However, this post won’t cover that.
Creating the build step
We need to create a series of commands for CodeBuild to execute. This can be anything depending on your situation. These steps are expressed in a file called buildspec.yml
. We can change the name of the file. But I’ll use buildspec.yml
as it’s the default name.
Now, where should I put this buildspec.yml
? Anywhere in your repository. You can instruct CodeBuild where to look for your buildspec.yml
later (I’m going to put it in the root of my React project).
Before we dive into the details, here is my buildspec.yml
.
version: 0.2
phases:
install:
runtime-versions:
nodejs: 10
commands:
- npm install
build:
commands:
- npm run test
- npm run build
- aws s3 cp ./build s3://$WEBSITE_BUCKET --recursive
- aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths /*
From the steps above, you can see we have several commands:
- we’re using NodeJS environment;
- install dependencies;
- test our application;
- build our application;
- copy our build folder to our S3 bucket;
- invalidate CloudFront cache (so CloudFront will pick up our updates);
And that’s it!
But…. Where do we define $WEBSITE_BUCKET
and $CLOUDFRONT_DISTRIBUTION_ID
?
We’re going to use environment variables!
You can use .json
file to specify that. I’m going to call my file build-dev.json
(for development). Here’s the content of build-dev.json
.
{
"projectName": "example-app-deploy",
"sourceVersion": "master",
"environmentVariablesOverride": [
{ "name": "STAGE", "value": "dev", "type": "PLAINTEXT" },
{ "name": "WEBSITE_BUCKET", "value": "example-app-dev-frontpagewebsitebucket-19jr14sozhbv6", "type": "PLAINTEXT" },
{ "name": "CLOUDFRONT_DISTRIBUTION_ID", "value": "E12MWEG8DY8FNG", "type": "PLAINTEXT" }
],
"buildspecOverride": "./buildspec.yml"
}
Now we can see the value of those environment variables. $WEBSITE_BUCKET
will be our bucket name and $CLOUDFRONT_DISTRIBUTION_ID
will be our CloudFront distribution ID. Obviously, you need to change the value to your resources.
projectName
is the name of your CloudBuild project. In this case I’m naming it example-app-deploy
.
buildspecOverride
can be used to point CodeBuild to your buildspec.yml
. I’m storing the buildspec.yml
in the project’s root folder.
sourceVersion
will specify which branch are you building from (although it’s not just branches).

Later, you can run the build by using this command:
aws codebuild start-build --cli-input-json file://build-dev.json
I like to register the command to npm scripts.
I’m going to call it deploy:dev
. So, in our package.json, I’d have something like this:
{
.... some other blocks ....
"scripts": {
.... some other scripts ....
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"deploy:dev": "aws codebuild start-build --cli-input-json file://build-dev.json"
},
.... some other blocks ....
}
Now everything’s connected!
We will issue a build command to our CodeBuild project with parameters from build-dev.json
. Then, CodeBuild will execute the commands you specify in buildspec.yml
.
The End
Phew! Finally!
The only thing you need to do now is to deploy it! Run this command in the folder that contains serverless.yml
(S3 + CloudFront):
sls deploy
It will take some time to finish..
Let’s try it! Upload the content of React build
folder / Angular dist
folder / a sample index.html
to your bucket.
Then, try to access it via your CloudFront’s domain. You can find it in AWS Console
> CloudFront
> Distributions
> Look at the Domain Name
column. It should work.
Then, try to access it via your S3 URL (e.g. http://{website-bucket}.s3-website-ap-southeast-2.amazonaws.com/index.html
). It shouldn’t work.
Yay!
If you’re using CodeBuild, run
sls deploy
in your folder which contains CodeBuild resources. It will create a CodeBuild project for you.To test the one-step deployment thing, run
yarn deploy:dev
in your React project. You can watch the build process inAWS Console
>CodeBuild
>Build History
> Pick the latest (top) one.
Now for the copy-pasting job. Here’s the complete serverless.yml
.
service:
name: example-app
provider:
name: aws
region: ap-southeast-2
resources:
Resources:
FrontPageWebsiteBucket:
Type: AWS::S3::Bucket
FrontPageWebsiteBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref FrontPageWebsiteBucket
PolicyDocument:
Statement:
- Effect: Allow
Action:
- s3:GetObject
Resource:
Fn::Join:
- /
- - Fn::GetAtt:
- FrontPageWebsiteBucket
- Arn
- '*'
Principal:
CanonicalUser:
Fn::GetAtt:
- FrontPageWebsiteOriginAccessIdentity
- S3CanonicalUserId
FrontPageWebsiteOriginAccessIdentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: Origin Access Identity to Access Website Bucket
FrontPageCloudFront:
Type: AWS::CloudFront::Distribution
DependsOn:
- FrontPageWebsiteBucket
Properties:
DistributionConfig:
Origins:
- DomainName:
Fn::GetAtt:
- FrontPageWebsiteBucket
- DomainName
Id: S3Origin
S3OriginConfig:
OriginAccessIdentity:
Fn::Join:
- /
- - origin-access-identity
- cloudfront
- !Ref FrontPageWebsiteOriginAccessIdentity
CustomErrorResponses:
- ErrorCachingMinTTL: 0
ErrorCode: 403
ResponseCode: 200
ResponsePagePath: /index.html
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
Compress: true
ForwardedValues:
QueryString: true
Cookies:
Forward: none
TargetOriginId: S3Origin
ViewerProtocolPolicy: redirect-to-https
Comment: my example website in s3
DefaultRootObject: index.html
Enabled: true
HttpVersion: http2
PriceClass: PriceClass_All
ViewerCertificate:
CloudFrontDefaultCertificate: true
With custom domain name:
service:
name: example-app
provider:
name: aws
region: ap-southeast-2
resources:
Resources:
FrontPageWebsiteBucket:
Type: AWS::S3::Bucket
FrontPageWebsiteBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref FrontPageWebsiteBucket
PolicyDocument:
Statement:
- Effect: Allow
Action:
- s3:GetObject
Resource:
Fn::Join:
- /
- - Fn::GetAtt:
- FrontPageWebsiteBucket
- Arn
- '*'
Principal:
CanonicalUser:
Fn::GetAtt:
- FrontPageWebsiteOriginAccessIdentity
- S3CanonicalUserId
FrontPageWebsiteOriginAccessIdentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: Origin Access Identity to Access Website Bucket
FrontPageCloudFront:
Type: AWS::CloudFront::Distribution
DependsOn:
- FrontPageWebsiteBucket
Properties:
DistributionConfig:
Origins:
- DomainName:
Fn::GetAtt:
- FrontPageWebsiteBucket
- DomainName
Id: S3Origin
S3OriginConfig:
OriginAccessIdentity:
Fn::Join:
- /
- - origin-access-identity
- cloudfront
- !Ref FrontPageWebsiteOriginAccessIdentity
CustomErrorResponses:
- ErrorCachingMinTTL: 0
ErrorCode: 403
ResponseCode: 200
ResponsePagePath: /index.html
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
Compress: true
ForwardedValues:
QueryString: true
Cookies:
Forward: none
TargetOriginId: S3Origin
ViewerProtocolPolicy: redirect-to-https
Comment: my example website in s3
DefaultRootObject: index.html
Enabled: true
HttpVersion: http2
PriceClass: PriceClass_All
ViewerCertificate:
AcmCertificateArn: arn:aws:acm:us-east-1:....
MinimumProtocolVersion: TLSv1.1_2016
SslSupportMethod: sni-only
Aliases:
- example.com
FrontPageDNSName:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneName: example.com.
RecordSets:
- Name: example.com
Type: A
AliasTarget:
HostedZoneId: Z2FDTNDATAQYW2 #cloudfront hostedzone id
DNSName:
Fn::GetAtt:
- FrontPageCloudFront
- DomainName
It’s not working!
While creating this post, I experienced an unusual error. Once I accessed my CloudFront URL (e.g. xxxxxx.cloudfront.net), I got redirected (HTTP 307) to an S3 URL (e.g. my-bucket.s3-ap-southeast-2.amazonaws.com/index.html) and it’s throwing me HTTP 403 error.
After a moment of googling, I arrived at a thread that explains the error. The DNS record for your newly created bucket is not propagated yet.
The solution? Patience. It says S3 could take an hour to be ready.
However, I couldn’t confirm this as I ended up redeploying the stack and left it for hours before I upload any files to the S3 Bucket and access it via CloudFront’s domain. But hey, at least it’s working now!
Another alternative is to use S3’s RegionalDomainName
instead of DomainName
. Change that in the Origins
section:
Origins:
- DomainName:
Fn::GetAtt:
- FrontPageWebsiteBucket
- RegionalDomainName
Id: S3Origin
S3OriginConfig:
OriginAccessIdentity:
Fn::Join:
- /
- - origin-access-identity
- cloudfront
- !Ref FrontPageWebsiteOriginAccessIdentity
If you have any tricks regarding this please let me know!
No Comments