Website Development || Software Development || Mobile App || Web App || Web Design || Cloud Services

Deploy a Serverless static website on AWS S3 and CloudFront using CloudFormation

891 Views 5 min read January 20, 2025 Updated: January 20, 2025

The design encompassing all the resources we’re about to generate is as follows:

Resources for AWS static website

In this blog post, we’ll delve into the following key topics:

  • Discovering the streamlined deployment of AWS resources using AWS CloudFormation through a single command, complemented by the convenience of a Makefile.
  • Finding a comprehensive reference to my GitHub repository, housing essential resources like the AWS CloudFormation template, a Makefile, and a starter static website template. This GitHub reference offers a valuable starting point for your resource deployment journey.
  • Exploring a reference for manual resource creation using the AWS Management Console, catering to those who prefer a hands-on approach.

Note: All the resources that we are going to create will be almost free of cost provided that the visitor on the website will be low

Prerequisite:

  1. A route53 hosted zone. You can create one by going on AWS Route53 in AWS Management Console and selecting create hosted zone as shown in the screenshot below.
AWS Route53 in AWS Management Console

2. An SSL certificate. You can create one by going on AWS Certificate Manager(ACM) in AWS Management console and clicking the Request a certificate button as shown in the screenshot below.

AWS Certificate Manager in AWS Management Console

How to use AWS CloudFormation to deploy AWS resources manually using AWS Management console?

Deploying AWS resources using AWS CloudFormation is a streamlined process that allows you to define and provision infrastructure as code. Here’s a concise guide on using AWS CloudFormation for resource deployment:

Create a Template: Craft a CloudFormation template in YAML or JSON, describing the desired resources, their properties, and relationships.

Stack Creation: In the AWS Management Console or through the AWS CLI, initiate the CloudFormation stack creation process. Provide the template, choose a stack name, and customize parameters.

Create stack AWS management console UI

If you are using s3 bucket provide the bucket URL in the Amazon S3 URL field. Otherwise, you can use Upload a template file option to directly upload template file.

Then in the upcoming steps you can fill in the necessary information and submit the form.

Note: In step 3 in the UI above there is the option to keep IAM role to control the access of the cloudformation stack that you are going to make. You can either create a role manually and assign the role name or role arn or keep it as blank.

CloudFormation permission management form field

Using CloudFormation simplifies resource provisioning, improves consistency, and enhances version control, making it a powerful tool in your AWS toolkit.

Deploy the resource for your static site using CLI:

Prerequisite: AWS CLI v2 installed.

AWSTemplateFormatVersion: 2010-09-09
Description: Static website hosting with S3 and CloudFront with a custom domain.
Parameters:
    Cert:
        Description: SSL Certificate ARN
        Type: String
    HostedZoneResourceID:
        Description: Hosted Zone ID
        Type: String
    DomainName:
        Description: Website Domain Name
        Type: String
    ErrorPagePath:
        Description: Directory error path
        Type: String
        Default: /error.html
    IndexDocument:
        Description: Directory index path
        Type: String
        Default: /index.html

Resources:
    S3Bucket:
        Type: "AWS::S3::Bucket"
        Properties:
            BucketName: !Sub ${DomainName}-cloudfront
    CloudFrontOriginAccessIdentity:
        Type: "AWS::CloudFront::CloudFrontOriginAccessIdentity"
        Properties:
            CloudFrontOriginAccessIdentityConfig:
                Comment: !Ref S3Bucket
    ReadPolicy:
        Type: "AWS::S3::BucketPolicy"
        Properties:
            Bucket: !Ref S3Bucket
            PolicyDocument:
                Statement:
                    - Action: "s3:GetObject"
                      Effect: Allow
                      Resource:
                          - !Sub "${S3Bucket.Arn}"
                          - !Sub "${S3Bucket.Arn}/*"
                      Principal:
                          CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId
    CloudFrontDistribution:
        Type: "AWS::CloudFront::Distribution"
        Properties:
            DistributionConfig:
                Aliases:
                    - !Ref DomainName
                ViewerCertificate:
                    AcmCertificateArn: !Ref Cert
                    SslSupportMethod: sni-only
                CustomErrorResponses:
                    - ErrorCode: 403
                      ResponseCode: 404
                      ResponsePagePath: !Ref ErrorPagePath
                DefaultCacheBehavior:
                    AllowedMethods:
                        - GET
                        - HEAD
                        - OPTIONS
                    CachedMethods:
                        - GET
                        - HEAD
                        - OPTIONS
                    Compress: true
                    DefaultTTL: 3600
                    ForwardedValues:
                        Cookies:
                            Forward: none
                        QueryString: false
                    MaxTTL: 86400
                    MinTTL: 60
                    TargetOriginId: s3origin
                    ViewerProtocolPolicy: redirect-to-https
                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
    PublishUser:
        Type: "AWS::IAM::User"
        Properties:
            Policies:
                - PolicyName: !Sub "publish-to-${S3Bucket}"
                  PolicyDocument:
                      Statement:
                          - Action: "s3:*"
                            Effect: Allow
                            Resource:
                                - !Sub "${S3Bucket.Arn}"
                                - !Sub "${S3Bucket.Arn}/*"
    DNSRecord:
        Type: AWS::Route53::RecordSet
        Properties:
            HostedZoneId: !Ref HostedZoneResourceID
            Comment: DNS name for cloud front
            Name: !Ref DomainName
            Type: A
            AliasTarget:
                HostedZoneId: Z2FDTNDATAQYW2
                DNSName: !GetAtt CloudFrontDistribution.DomainName
        DependsOn: CloudFrontDistribution

Outputs:
    BucketName:
        Description: S3 Bucket Name
        Value: !Ref S3Bucket
    PublishUser:
        Description: IAM User with write access to the bucket
        Value: !Ref PublishUser
    URL:
        Description: Website URL
        Value: !Ref DNSRecord

This CloudFormation YAML script sets up a secure static website using AWS resources. It creates an S3 bucket for website content, sets up CloudFront distribution with SSL, handles custom error pages, establishes IAM user permissions for publishing, and configures DNS records for a custom domain. The template ensures a streamlined deployment process for hosting a secure static website on AWS.

You can save this script in a file and name it as cloudformation.yaml and create the resource using the cli command:

aws cloudformation create-stack --region us-east-1 --stack-name $(STACK_NAME) --template-body $(STACK_BODY) --parameters ParameterKey=Cert,ParameterValue=$(CERT) ParameterKey=HostedZoneResourceID,ParameterValue=$(HostedZoneResourceID) ParameterKey=DomainName,ParameterValue=$(DomainName) --capabilities=CAPABILITY_NAMED_IAM --profile $(Profile)

Note: You have to replace the variables starting with $ sign in the above command as per the requirement

Tags
Share This Article